From 31b07e7353ecfd54a61a9734c15319c51e98d966 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 26 Jul 2023 08:48:32 +0200 Subject: [PATCH 01/48] docs: fix minor spelling error --- docs/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 2c13f9fe..e358cb7f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -84,18 +84,18 @@ The following arguments are supported in the `provider` block: - **sshkey_pem** (Optional, String) This is the ssh key in PEM format for establish ssh connection. It can also be sourced from the `JUNOS_KEYPEM` environment variable. - Defaults is empty. + Defaults to empty. - **sshkeyfile** (Optional, String) This is the path to ssh key for establish ssh connection. Used only if `sshkey_pem` is empty. It can also be sourced from the `JUNOS_KEYFILE` environment variable. - Defaults is empty. + Defaults to empty. - **password** (Optional, String) This is a password for ssh connection. It can also be sourced from the `JUNOS_PASSWORD` environment variable. - Defaults is empty. + Defaults to empty. - **port** (Optional, Number) This is the tcp port for ssh connection. @@ -105,7 +105,7 @@ The following arguments are supported in the `provider` block: - **keypass** (Optional, String) This is the passphrase for open `sshkeyfile` or `sshkey_pem`. It can also be sourced from the `JUNOS_KEYPASS` environment variable. - Defaults is empty. + Defaults to empty. - **group_interface_delete** (Optional, String) This is the Junos group used to remove configuration on a physical interface. @@ -177,7 +177,7 @@ The following arguments are supported in the `provider` block: - **debug_netconf_log_path** (Optional, String) More detailed log (netconf) in the specified file. It can also be sourced from the `JUNOS_LOG_PATH` environment variable. - Defaults is empty. + Defaults to empty. ~> **NOTE:** If this option is used (not empty), all Junos commands are logged in this file, therefore there may be sensitive data in plain text in the file. @@ -208,7 +208,7 @@ The following arguments are supported in the `provider` block: It can also be sourced from the `JUNOS_FAKECREATE_SETFILE` environment variable. - Defaults is empty. + Defaults to empty. ~> **NOTE:** If this option is used (not empty), all Junos commands are added to this file, therefore there may be sensitive data in plain text in the file. @@ -229,7 +229,7 @@ The following arguments are supported in the `provider` block: It can also be sourced from the `JUNOS_FAKEUPDATE_ALSO` environment variable and its value is `true`. - Defaults is `false`. + Defaults to `false`. - **fake_delete_also** (Optional, Boolean, **don't use in normal terraform run**) As with `create` and `fake_create_with_setfile`, when this option is true, the normal @@ -245,7 +245,7 @@ The following arguments are supported in the `provider` block: It can also be sourced from the `JUNOS_FAKEDELETE_ALSO` environment variable and its value is `true`. - Defaults is `false`. + Defaults to `false`. ## Interface specifications From 97fc39d890bb207112042d1a4ec4e1b7f9173383 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 31 Jul 2023 10:47:36 +0200 Subject: [PATCH 02/48] r/security_address_book: fix word mistake in error message --- internal/providerfwk/resource_security_address_book.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providerfwk/resource_security_address_book.go b/internal/providerfwk/resource_security_address_book.go index 8aba2bd3..58710147 100644 --- a/internal/providerfwk/resource_security_address_book.go +++ b/internal/providerfwk/resource_security_address_book.go @@ -575,7 +575,7 @@ func (rsc *securityAddressBook) Create( if !junSess.CheckCompatibilitySecurity() { resp.Diagnostics.AddError( tfdiag.CompatibilityErrSummary, - fmt.Sprintf("security policy not compatible "+ + fmt.Sprintf(rsc.junosName()+" not compatible "+ "with Junos device %q", junSess.SystemInformation.HardwareModel), ) From c866b852510575a04a7b067360a5e8550fa4cceb Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 2 Aug 2023 09:10:45 +0200 Subject: [PATCH 03/48] r/security_nat_destination (& _pool): use new provider via framework --- .changes/security-nat-with-fwk.md | 9 + docs/resources/security_nat_destination.md | 30 +- .../security_nat_destination_pool.md | 10 +- internal/providerfwk/provider.go | 2 + .../resource_security_nat_destination.go | 740 ++++++++++++++++++ .../resource_security_nat_destination_pool.go | 417 ++++++++++ .../resource_security_nat_destination_test.go | 22 +- .../upgradestate_security_nat_destination.go | 148 ++++ ...radestate_security_nat_destination_test.go | 62 ++ internal/providersdk/provider.go | 2 - .../resource_security_nat_destination.go | 542 ------------- .../resource_security_nat_destination_pool.go | 374 --------- internal/tfvalidator/string_net_test.go | 8 + 13 files changed, 1413 insertions(+), 953 deletions(-) create mode 100644 .changes/security-nat-with-fwk.md create mode 100644 internal/providerfwk/resource_security_nat_destination.go create mode 100644 internal/providerfwk/resource_security_nat_destination_pool.go rename internal/{providersdk => providerfwk}/resource_security_nat_destination_test.go (88%) create mode 100644 internal/providerfwk/upgradestate_security_nat_destination.go create mode 100644 internal/providerfwk/upgradestate_security_nat_destination_test.go delete mode 100644 internal/providersdk/resource_security_nat_destination.go delete mode 100644 internal/providersdk/resource_security_nat_destination_pool.go diff --git a/.changes/security-nat-with-fwk.md b/.changes/security-nat-with-fwk.md new file mode 100644 index 00000000..ee374033 --- /dev/null +++ b/.changes/security-nat-with-fwk.md @@ -0,0 +1,9 @@ + +ENHANCEMENTS: + +* **resource/junos_security_nat_destination**: 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 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_security_nat_destination_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional string attributes doesn't accept *empty* value diff --git a/docs/resources/security_nat_destination.md b/docs/resources/security_nat_destination.md index c2d540b5..8f1244d9 100644 --- a/docs/resources/security_nat_destination.md +++ b/docs/resources/security_nat_destination.md @@ -32,19 +32,19 @@ resource "junos_security_nat_destination" "demo_dnat" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of destination nat. + Destination nat rule-set name. - **from** (Required, Block) - Declare `from` configuration. + Declare where is the traffic from. - **type** (Required, String) - Type of from options. + Type of traffic source. Need to be `interface`, `routing-instance` or `zone` - **value** (Required, Set of String) - Name of interface, routing-instance or zone for from options + Name of interface, routing-instance or zone for traffic source. - **rule** (Required, Block List) - For each name of rule to declare. + For each name of destination nat rule to declare. See [below for nested schema](#rule-arguments). - **description** (Optional, String) - Text description of rule set + Text description of destination nat rule-set. --- @@ -53,29 +53,29 @@ The following arguments are supported: -> **Note:** One of `destination_address` or `destination_address_name` arguments is required. - **name** (Required, String) - Name of rule + Rule name. - **destination_address** (Optional, String) - CIDR for match destination address + CIDR destination address to match. - **destination_address_name** (Optional, String) - Destination address from address book for rule match. + Destination address from address book to match. - **application** (Optional, Set of String) - Specify application or application-set name for rule match. + Application or application-set name to match. - **destination_port** (Optional, Set of String) - List of destination port for rule match. + Destination port to match. Format need to be `x` or `x to y`. - **protocol** (Optional, Set of String) - IP Protocol for rule match. + IP Protocol to match. - **source_address** (Optional, Set of String) - List of CIDR source address for rule match. + CIDR source address to match. - **source_address_name** (Optional, Set of String) - List of source address from address book for rule match. + Source address from address book to match. - **then** (Required, Block) Declare `then` action. - **type** (Required, String) Type of destination nat. Need to be `pool` or `off` - **pool** (Optional, String) - Name of nat destination pool when type pool + Name of destination nat pool when type is pool. ## Attributes Reference diff --git a/docs/resources/security_nat_destination_pool.md b/docs/resources/security_nat_destination_pool.md index e2629b48..124637b9 100644 --- a/docs/resources/security_nat_destination_pool.md +++ b/docs/resources/security_nat_destination_pool.md @@ -21,18 +21,18 @@ resource "junos_security_nat_destination_pool" "demo_dnat_pool" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of destination nat pool. + Pool name. - **address** (Required, String) - IP/mask for destination nat pool. + CIDR address to destination nat pool. - **address_port** (Optional, Number) Port change too with destination nat. Conflict with `address_to`. - **address_to** (Optional, String) - IP/mask for range of destination nat pool (range = `address` to `address_to`). + CIDR to define range of address to destination nat pool (range = `address` to `address_to`). - **description** (Optional, String) - Text description of pool + Text description of pool. - **routing_instance** (Optional, String) - Name of routing instance for switch instance with nat. + Name of routing instance to switch instance with nat. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 8292d217..754e6cd7 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -227,6 +227,8 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newSecurityIpsecPolicyResource, newSecurityIpsecProposalResource, newSecurityIpsecVpnResource, + newSecurityNatDestinationResource, + newSecurityNatDestinationPoolResource, newSecurityPolicyResource, newSecurityPolicyTunnelPairPolicyResource, newSecurityZoneResource, diff --git a/internal/providerfwk/resource_security_nat_destination.go b/internal/providerfwk/resource_security_nat_destination.go new file mode 100644 index 00000000..6465ac1b --- /dev/null +++ b/internal/providerfwk/resource_security_nat_destination.go @@ -0,0 +1,740 @@ +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/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 = &securityNatDestination{} + _ resource.ResourceWithConfigure = &securityNatDestination{} + _ resource.ResourceWithValidateConfig = &securityNatDestination{} + _ resource.ResourceWithImportState = &securityNatDestination{} + _ resource.ResourceWithUpgradeState = &securityNatDestination{} +) + +type securityNatDestination struct { + client *junos.Client +} + +func newSecurityNatDestinationResource() resource.Resource { + return &securityNatDestination{} +} + +func (rsc *securityNatDestination) typeName() string { + return providerName + "_security_nat_destination" +} + +func (rsc *securityNatDestination) junosName() string { + return "security nat destination rule-set" +} + +func (rsc *securityNatDestination) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatDestination) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatDestination) 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 *securityNatDestination) 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: "Destination nat rule-set name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description of destination nat rule-set.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.SingleNestedBlock{ + Description: "Declare where is the traffic from.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of traffice source.", + Validators: []validator.String{ + stringvalidator.OneOf("interface", "routing-instance", "zone"), + }, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "Name of interface, routing-instance or zone for traffic source.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + ), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "rule": schema.ListNestedBlock{ + Description: "For each name of destination nat rule to declare.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Rule name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + Description: "CIDR destination address to match.", + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + Description: "Destination address from address book to match.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + }, + }, + "application": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Application or application-set name to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "destination_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Destination port to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d+( to \d+)?$`), + "must be use format `x` or `x to y`", + ), + ), + }, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "IP Protocol to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "CIDR source address to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source address from address book to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.SingleNestedBlock{ + Description: "Declare `then` action.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of destination nat.", + Validators: []validator.String{ + stringvalidator.OneOf("off", "pool"), + }, + }, + "pool": schema.StringAttribute{ + Optional: true, + Description: "Name of destination nat pool when type is pool.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + }, + } +} + +type securityNatDestinationData struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatDestinationBlockFrom `tfsdk:"from"` + Rule []securityNatDestinationBlockRule `tfsdk:"rule"` +} + +type securityNatDestinationConfig struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatDestinationBlockFromConfig `tfsdk:"from"` + Rule types.List `tfsdk:"rule"` +} + +type securityNatDestinationBlockFrom struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` +} + +type securityNatDestinationBlockFromConfig struct { + Type types.String `tfsdk:"type"` + Value types.Set `tfsdk:"value"` +} + +type securityNatDestinationBlockRule struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + Application []types.String `tfsdk:"application"` + DestinationPort []types.String `tfsdk:"destination_port"` + Protocol []types.String `tfsdk:"protocol"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + Then *securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` +} + +type securityNatDestinationBlockRuleConfig struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + Application types.Set `tfsdk:"application"` + DestinationPort types.Set `tfsdk:"destination_port"` + Protocol types.Set `tfsdk:"protocol"` + SourceAddress types.Set `tfsdk:"source_address"` + SourceAddressName types.Set `tfsdk:"source_address_name"` + Then *securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` +} + +type securityNatDestinationBlockRuleBlockThen struct { + Type types.String `tfsdk:"type"` + Pool types.String `tfsdk:"pool"` +} + +func (rsc *securityNatDestination) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatDestinationConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.Rule.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule"), + tfdiag.MissingConfigErrSummary, + "at least one rule block must be specified", + ) + } else if !config.Rule.IsUnknown() { + var rule []securityNatDestinationBlockRuleConfig + asDiags := config.Rule.ElementsAs(ctx, &rule, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + ruleName := make(map[string]struct{}) + for i, block := range rule { + if !block.Name.IsUnknown() { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("name"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple rule blocks with the same name %q", name), + ) + } + ruleName[name] = struct{}{} + } + if block.DestinationAddress.IsNull() && + block.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("destination_address"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("one of destination_address or destination_address_name must be specified"+ + " in rule block %q", block.Name.ValueString()), + ) + } + if !block.DestinationAddress.IsNull() && + !block.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("destination_address"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("destination_address and destination_address_name cannot be configured together"+ + " in rule block %q", block.Name.ValueString()), + ) + } + if block.Then != nil { + if !block.Then.Type.IsUnknown() && !block.Then.Pool.IsUnknown() { + if block.Then.Type.ValueString() == "pool" { + if block.Then.Pool.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("missing pool value to destination-nat pool"+ + " in rule block %q", block.Name.ValueString()), + ) + } + } + } + } + } + } +} + +func (rsc *securityNatDestination) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatDestinationData + 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 { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + natDestinationExists, err := checkSecurityNatDestinationExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if natDestinationExists { + 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 { + natDestinationExists, err := checkSecurityNatDestinationExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !natDestinationExists { + 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 *securityNatDestination) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatDestinationData + 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 *securityNatDestination) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatDestinationData + 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 *securityNatDestination) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatDestinationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatDestination) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data securityNatDestinationData + + 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 checkSecurityNatDestinationExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat destination rule-set " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatDestinationData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *securityNatDestinationData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatDestinationData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set security nat destination rule-set " + rscData.Name.ValueString() + " " + + regexpDestPort := regexp.MustCompile(`^\d+( to \d+)?$`) + + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } + if rscData.From != nil { + for _, v := range rscData.From.Value { + configSet = append(configSet, setPrefix+"from "+rscData.From.Type.ValueString()+" "+v.ValueString()) + } + } + ruleName := make(map[string]struct{}) + for i, block := range rscData.Rule { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + return path.Root("rule").AtListIndex(i).AtName("name"), + fmt.Errorf("multiple rule blocks with the same name %q", name) + } + ruleName[name] = struct{}{} + if block.DestinationAddress.IsNull() && + block.DestinationAddressName.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("destination_address"), + fmt.Errorf("one of destination_address or destination_address_name must be specified"+ + " in rule block %q", name) + } + if !block.DestinationAddress.IsNull() && + !block.DestinationAddressName.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("destination_address"), + fmt.Errorf("destination_address and destination_address_name cannot be configured together"+ + " in rule block %q", name) + } + setPrefixRule := setPrefix + "rule " + name + " " + if v := block.DestinationAddress.ValueString(); v != "" { + configSet = append(configSet, setPrefixRule+"match destination-address "+v) + } + if v := block.DestinationAddressName.ValueString(); v != "" { + configSet = append(configSet, setPrefixRule+"match destination-address-name \""+v+"\"") + } + for _, v := range block.Application { + configSet = append(configSet, setPrefixRule+"match application \""+v.ValueString()+"\"") + } + for _, v := range block.DestinationPort { + if !regexpDestPort.MatchString(v.ValueString()) { + return path.Root("rule").AtListIndex(i).AtName("destination_port"), + fmt.Errorf("destination_port must be use format `x` or `x to y`"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"match destination-port "+v.ValueString()) + } + for _, v := range block.Protocol { + configSet = append(configSet, setPrefixRule+"match protocol "+v.ValueString()) + } + for _, v := range block.SourceAddress { + configSet = append(configSet, setPrefixRule+"match source-address "+v.ValueString()) + } + for _, v := range block.SourceAddressName { + configSet = append(configSet, setPrefixRule+"match source-address-name \""+v.ValueString()+"\"") + } + if block.Then != nil { + switch block.Then.Type.ValueString() { + case "off": + configSet = append(configSet, setPrefixRule+"then destination-nat off") + case "pool": + pool := block.Then.Pool.ValueString() + if pool == "" { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("pool"), + fmt.Errorf("missing pool value to destination-nat pool"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"then destination-nat pool "+pool) + } + } + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatDestinationData) read( + _ context.Context, name string, junSess *junos.Session, +) error { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat destination rule-set " + 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, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "from "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { // + return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) + } + if rscData.From == nil { + rscData.From = &securityNatDestinationBlockFrom{ + Type: types.StringValue(itemTrimFields[0]), + } + } + rscData.From.Value = append(rscData.From.Value, types.StringValue(itemTrimFields[1])) + case balt.CutPrefixInString(&itemTrim, "rule "): + itemTrimFields := strings.Split(itemTrim, " ") + var rule securityNatDestinationBlockRule + rscData.Rule, rule = tfdata.ExtractBlockWithTFTypesString( + rscData.Rule, "Name", itemTrimFields[0], + ) + rule.Name = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + switch { + case balt.CutPrefixInString(&itemTrim, "match destination-address "): + rule.DestinationAddress = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): + rule.DestinationAddressName = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "match application "): + rule.Application = append(rule.Application, + types.StringValue(strings.Trim(itemTrim, "\"")), + ) + case balt.CutPrefixInString(&itemTrim, "match destination-port "): + rule.DestinationPort = append(rule.DestinationPort, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "match protocol "): + rule.Protocol = append(rule.Protocol, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "match source-address "): + rule.SourceAddress = append(rule.SourceAddress, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "match source-address-name "): + rule.SourceAddressName = append(rule.SourceAddressName, + types.StringValue(strings.Trim(itemTrim, "\"")), + ) + case balt.CutPrefixInString(&itemTrim, "then destination-nat "): + if rule.Then == nil { + rule.Then = &securityNatDestinationBlockRuleBlockThen{} + } + if balt.CutPrefixInString(&itemTrim, "pool ") { + rule.Then.Type = types.StringValue("pool") + rule.Then.Pool = types.StringValue(itemTrim) + } else { + rule.Then.Type = types.StringValue(itemTrim) + } + } + rscData.Rule = append(rscData.Rule, rule) + } + } + } + + return nil +} + +func (rscData *securityNatDestinationData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat destination rule-set " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providerfwk/resource_security_nat_destination_pool.go b/internal/providerfwk/resource_security_nat_destination_pool.go new file mode 100644 index 00000000..1f661ba7 --- /dev/null +++ b/internal/providerfwk/resource_security_nat_destination_pool.go @@ -0,0 +1,417 @@ +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/jeremmfr/terraform-provider-junos/internal/utils" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &securityNatDestinationPool{} + _ resource.ResourceWithConfigure = &securityNatDestinationPool{} + _ resource.ResourceWithValidateConfig = &securityNatDestinationPool{} + _ resource.ResourceWithImportState = &securityNatDestinationPool{} +) + +type securityNatDestinationPool struct { + client *junos.Client +} + +func newSecurityNatDestinationPoolResource() resource.Resource { + return &securityNatDestinationPool{} +} + +func (rsc *securityNatDestinationPool) typeName() string { + return providerName + "_security_nat_destination_pool" +} + +func (rsc *securityNatDestinationPool) junosName() string { + return "security nat destination pool" +} + +func (rsc *securityNatDestinationPool) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatDestinationPool) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatDestinationPool) 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 *securityNatDestinationPool) 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: "Pool name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "address": schema.StringAttribute{ + Required: true, + Description: "CIDR address to destination nat pool.", + Validators: []validator.String{ + tfvalidator.StringCIDR(), + }, + }, + "address_port": schema.Int64Attribute{ + Optional: true, + Description: "Port change too with destination nat.", + Validators: []validator.Int64{ + int64validator.Between(0, 65535), + }, + }, + "address_to": schema.StringAttribute{ + Optional: true, + Description: "CIDR to define range of address to destination nat pool.", + Validators: []validator.String{ + tfvalidator.StringCIDR(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description of pool.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Name of routing instance to switch instance with nat.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + } +} + +type securityNatDestinationPoolData struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Address types.String `tfsdk:"address"` + AddressPort types.Int64 `tfsdk:"address_port"` + AddressTo types.String `tfsdk:"address_to"` + Description types.String `tfsdk:"description"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +func (rsc *securityNatDestinationPool) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatDestinationPoolData + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.AddressPort.IsNull() && + !config.AddressTo.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("address_port"), + tfdiag.ConflictConfigErrSummary, + "address_port and address_to cannot be configured together", + ) + } +} + +func (rsc *securityNatDestinationPool) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatDestinationPoolData + 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 { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + poolExists, err := checkSecurityNatDestinationPoolExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if poolExists { + 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 { + poolExists, err := checkSecurityNatDestinationPoolExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !poolExists { + 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 *securityNatDestinationPool) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatDestinationPoolData + 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 *securityNatDestinationPool) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatDestinationPoolData + 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 *securityNatDestinationPool) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatDestinationPoolData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatDestinationPool) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data securityNatDestinationPoolData + + 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 checkSecurityNatDestinationPoolExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat destination pool " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatDestinationPoolData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *securityNatDestinationPoolData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatDestinationPoolData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := "set security nat destination pool " + rscData.Name.ValueString() + " " + configSet := []string{ + setPrefix + "address " + rscData.Address.ValueString(), + } + if !rscData.AddressPort.IsNull() { + configSet = append(configSet, setPrefix+ + "address port "+utils.ConvI64toa(rscData.AddressPort.ValueInt64())) + } + if v := rscData.AddressTo.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"address to "+v) + } + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } + if v := rscData.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"routing-instance "+v) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatDestinationPoolData) read( + _ context.Context, name string, junSess *junos.Session, +) error { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat destination pool " + 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, "address port "): + var err error + rscData.AddressPort, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "address to "): + rscData.AddressTo = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "address "): + rscData.Address = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "routing-instance "): + rscData.RoutingInstance = types.StringValue(itemTrim) + } + } + } + + return nil +} + +func (rscData *securityNatDestinationPoolData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat destination pool " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_security_nat_destination_test.go b/internal/providerfwk/resource_security_nat_destination_test.go similarity index 88% rename from internal/providersdk/resource_security_nat_destination_test.go rename to internal/providerfwk/resource_security_nat_destination_test.go index 2caf9726..b35eb3c3 100644 --- a/internal/providersdk/resource_security_nat_destination_test.go +++ b/internal/providerfwk/resource_security_nat_destination_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 TestAccJunosSecurityNatDestination_basic(t *testing.T) { @@ -17,11 +17,9 @@ func TestAccJunosSecurityNatDestination_basic(t *testing.T) { Config: testAccJunosSecurityNatDestinationConfigCreate(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "from.#", "1"), + "from.type", "zone"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "from.0.type", "zone"), - resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "from.0.value.0", "testacc_securityDNAT"), + "from.value.0", "testacc_securityDNAT"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", "rule.#", "1"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", @@ -29,13 +27,9 @@ func TestAccJunosSecurityNatDestination_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", "rule.0.destination_address", "192.0.2.1/32"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.0.then.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.0.then.0.type", "pool"), + "rule.0.then.type", "pool"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.0.then.0.pool", "testacc_securityDNATPool"), - resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.0.then.0.pool", "testacc_securityDNATPool"), + "rule.0.then.pool", "testacc_securityDNATPool"), resource.TestCheckResourceAttr("junos_security_nat_destination_pool.testacc_securityDNATPool", "address", "192.0.2.1/32"), resource.TestCheckResourceAttr("junos_security_nat_destination_pool.testacc_securityDNATPool", @@ -54,9 +48,7 @@ func TestAccJunosSecurityNatDestination_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", "rule.1.destination_address_name", "testacc_securityDNAT"), resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.1.then.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_destination.testacc_securityDNAT", - "rule.1.then.0.type", "pool"), + "rule.1.then.type", "pool"), ), }, { diff --git a/internal/providerfwk/upgradestate_security_nat_destination.go b/internal/providerfwk/upgradestate_security_nat_destination.go new file mode 100644 index 00000000..1705140d --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_destination.go @@ -0,0 +1,148 @@ +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 *securityNatDestination) 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, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + "rule": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + }, + "application": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "destination_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "pool": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeSecurityNatDestinationV0toV1, + }, + } +} + +func upgradeSecurityNatDestinationV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []securityNatDestinationBlockFrom `tfsdk:"from"` + Rule []struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + Application []types.String `tfsdk:"application"` + DestinationPort []types.String `tfsdk:"destination_port"` + Protocol []types.String `tfsdk:"protocol"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + Then []securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` + } `tfsdk:"rule"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 securityNatDestinationData + dataV1.ID = dataV0.ID + dataV1.Name = dataV0.Name + dataV1.Description = dataV0.Description + if len(dataV0.From) > 0 { + dataV1.From = &dataV0.From[0] + } + for _, blockV0 := range dataV0.Rule { + blockV1 := securityNatDestinationBlockRule{ + Name: blockV0.Name, + DestinationAddress: blockV0.DestinationAddress, + DestinationAddressName: blockV0.DestinationAddressName, + Application: blockV0.Application, + DestinationPort: blockV0.DestinationPort, + Protocol: blockV0.Protocol, + SourceAddress: blockV0.SourceAddress, + SourceAddressName: blockV0.SourceAddressName, + } + if len(blockV0.Then) > 0 { + blockV1.Then = &blockV0.Then[0] + } + dataV1.Rule = append(dataV1.Rule, blockV1) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_security_nat_destination_test.go b/internal/providerfwk/upgradestate_security_nat_destination_test.go new file mode 100644 index 00000000..78b45310 --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_destination_test.go @@ -0,0 +1,62 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosSecurityNatDestinationUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_UPGRADE_STATE") == "" { + return + } + if os.Getenv("TESTACC_SRX") != "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosSecurityNatDestinationConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosSecurityNatDestinationConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosSecurityNatDestinationConfigV0() string { + return ` +resource "junos_security_nat_destination" "testacc_securityDNAT" { + name = "testacc_securityDNAT_upgrade" + description = "testacc securityDNAT upgrade" + from { + type = "zone" + value = [junos_security_zone.testacc_securityDNAT_upgrade.name] + } + rule { + name = "testacc_securityDNATRule" + destination_address = "192.0.2.1/32" + then { + type = "off" + } + } +} +resource "junos_security_zone" "testacc_securityDNAT_upgrade" { + name = "testacc_securityDNAT_upgrade" +} +` +} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 6e651616..2c2241f4 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -178,8 +178,6 @@ func Provider() *schema.Provider { "junos_security_idp_custom_attack_group": resourceSecurityIdpCustomAttackGroup(), "junos_security_idp_policy": resourceSecurityIdpPolicy(), "junos_security_log_stream": resourceSecurityLogStream(), - "junos_security_nat_destination": resourceSecurityNatDestination(), - "junos_security_nat_destination_pool": resourceSecurityNatDestinationPool(), "junos_security_nat_source": resourceSecurityNatSource(), "junos_security_nat_source_pool": resourceSecurityNatSourcePool(), "junos_security_nat_static": resourceSecurityNatStatic(), diff --git a/internal/providersdk/resource_security_nat_destination.go b/internal/providersdk/resource_security_nat_destination.go deleted file mode 100644 index b7185923..00000000 --- a/internal/providersdk/resource_security_nat_destination.go +++ /dev/null @@ -1,542 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "regexp" - "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" - bchk "github.com/jeremmfr/go-utils/basiccheck" -) - -type natDestinationOptions struct { - name string - description string - from []map[string]interface{} - rule []map[string]interface{} -} - -func resourceSecurityNatDestination() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatDestinationCreate, - ReadWithoutTimeout: resourceSecurityNatDestinationRead, - UpdateWithoutTimeout: resourceSecurityNatDestinationUpdate, - DeleteWithoutTimeout: resourceSecurityNatDestinationDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatDestinationImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "from": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"interface", "routing-instance", "zone"}, false), - }, - "value": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "rule": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "destination_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "destination_address_name": { - Type: schema.TypeString, - Optional: true, - }, - "application": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "destination_port": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "protocol": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_address": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - "source_address_name": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "then": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"off", "pool"}, false), - }, - "pool": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - }, - }, - }, - }, - }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourceSecurityNatDestinationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatDestination(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 !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat destination not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - securityNatDestinationExists, err := checkSecurityNatDestinationExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatDestinationExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("security nat destination %v already exists", d.Get("name").(string)))...) - } - - if err := setSecurityNatDestination(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_destination") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - securityNatDestinationExists, err = checkSecurityNatDestinationExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatDestinationExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat destination %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourceSecurityNatDestinationReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatDestinationRead(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 resourceSecurityNatDestinationReadWJunSess(d, junSess) -} - -func resourceSecurityNatDestinationReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natDestinationOptions, err := readSecurityNatDestination(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natDestinationOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatDestinationData(d, natDestinationOptions) - } - - return nil -} - -func resourceSecurityNatDestinationUpdate(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 := delSecurityNatDestination(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setSecurityNatDestination(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 := delSecurityNatDestination(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setSecurityNatDestination(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_destination") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatDestinationReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatDestinationDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatDestination(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 := delSecurityNatDestination(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_destination") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatDestinationImport(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) - - securityNatDestinationExists, err := checkSecurityNatDestinationExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !securityNatDestinationExists { - return nil, fmt.Errorf("don't find nat destination with id '%v' (id must be )", d.Id()) - } - natDestinationOptions, err := readSecurityNatDestination(d.Id(), junSess) - if err != nil { - return nil, err - } - fillSecurityNatDestinationData(d, natDestinationOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatDestinationExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat destination rule-set " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatDestination(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - regexpDestPort := regexp.MustCompile(`^\d+( to \d+)?$`) - - setPrefix := "set security nat destination rule-set " + d.Get("name").(string) - for _, v := range d.Get("from").([]interface{}) { - from := v.(map[string]interface{}) - for _, value := range sortSetOfString(from["value"].(*schema.Set).List()) { - configSet = append(configSet, setPrefix+" from "+from["type"].(string)+" "+value) - } - } - ruleNameList := make([]string, 0) - for _, v := range d.Get("rule").([]interface{}) { - rule := v.(map[string]interface{}) - if bchk.InSlice(rule["name"].(string), ruleNameList) { - return fmt.Errorf("multiple blocks rule with the same name %s", rule["name"].(string)) - } - ruleNameList = append(ruleNameList, rule["name"].(string)) - setPrefixRule := setPrefix + " rule " + rule["name"].(string) - if rule["destination_address"].(string) == "" && rule["destination_address_name"].(string) == "" { - return fmt.Errorf("missing destination_address or destination_address_name in rule %s", rule["name"].(string)) - } - if rule["destination_address"].(string) != "" && rule["destination_address_name"].(string) != "" { - return fmt.Errorf("destination_address and destination_address_name must not be set at the same time "+ - "in rule %s", rule["name"].(string)) - } - if vv := rule["destination_address"].(string); vv != "" { - configSet = append(configSet, setPrefixRule+" match destination-address "+vv) - } - if vv := rule["destination_address_name"].(string); vv != "" { - configSet = append(configSet, setPrefixRule+" match destination-address-name \""+vv+"\"") - } - for _, vv := range sortSetOfString(rule["application"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match application \""+vv+"\"") - } - for _, vv := range sortSetOfString(rule["destination_port"].(*schema.Set).List()) { - if !regexpDestPort.MatchString(vv) { - return fmt.Errorf("destination_port need to have format `x` or `x to y` in rule %s", rule["name"].(string)) - } - configSet = append(configSet, setPrefixRule+" match destination-port "+vv) - } - for _, vv := range sortSetOfString(rule["protocol"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match protocol "+vv) - } - for _, vv := range sortSetOfString(rule["source_address"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address "+vv) - } - for _, vv := range sortSetOfString(rule["source_address_name"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address-name \""+vv+"\"") - } - for _, thenV := range rule["then"].([]interface{}) { - then := thenV.(map[string]interface{}) - if then["type"].(string) == "off" { - configSet = append(configSet, setPrefixRule+" then destination-nat off") - } - if then["type"].(string) == "pool" { - if then["pool"].(string) == "" { - return fmt.Errorf("missing pool for destination-nat pool for rule %v in %v", - then["name"].(string), d.Get("name").(string)) - } - configSet = append(configSet, setPrefixRule+" then destination-nat pool "+then["pool"].(string)) - } - } - } - if v := d.Get("description").(string); v != "" { - configSet = append(configSet, setPrefix+" description \""+v+"\"") - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatDestination(name string, junSess *junos.Session, -) (confRead natDestinationOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat destination rule-set " + 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, "from "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { // - return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) - } - if len(confRead.from) == 0 { - confRead.from = append(confRead.from, map[string]interface{}{ - "type": itemTrimFields[0], - "value": make([]string, 0), - }) - } - confRead.from[0]["value"] = append(confRead.from[0]["value"].([]string), itemTrimFields[1]) - case balt.CutPrefixInString(&itemTrim, "rule "): - itemTrimFields := strings.Split(itemTrim, " ") - ruleOptions := map[string]interface{}{ - "name": itemTrimFields[0], - "destination_address": "", - "destination_address_name": "", - "application": make([]string, 0), - "destination_port": make([]string, 0), - "protocol": make([]string, 0), - "source_address": make([]string, 0), - "source_address_name": make([]string, 0), - "then": make([]map[string]interface{}, 0), - } - confRead.rule = copyAndRemoveItemMapList("name", ruleOptions, confRead.rule) - balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") - switch { - case balt.CutPrefixInString(&itemTrim, "match destination-address "): - ruleOptions["destination_address"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): - ruleOptions["destination_address_name"] = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "match application "): - ruleOptions["application"] = append( - ruleOptions["application"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "match destination-port "): - ruleOptions["destination_port"] = append( - ruleOptions["destination_port"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "match protocol "): - ruleOptions["protocol"] = append( - ruleOptions["protocol"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "match source-address "): - ruleOptions["source_address"] = append( - ruleOptions["source_address"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "match source-address-name "): - ruleOptions["source_address_name"] = append( - ruleOptions["source_address_name"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "then destination-nat "): - if len(ruleOptions["then"].([]map[string]interface{})) == 0 { - ruleOptions["then"] = append(ruleOptions["then"].([]map[string]interface{}), map[string]interface{}{ - "type": "", - "pool": "", - }) - } - ruleThenOptions := ruleOptions["then"].([]map[string]interface{})[0] - if balt.CutPrefixInString(&itemTrim, "pool ") { - ruleThenOptions["type"] = "pool" - ruleThenOptions["pool"] = itemTrim - } else { - ruleThenOptions["type"] = itemTrim - } - } - confRead.rule = append(confRead.rule, ruleOptions) - case balt.CutPrefixInString(&itemTrim, "description "): - confRead.description = strings.Trim(itemTrim, "\"") - } - } - } - - return confRead, nil -} - -func delSecurityNatDestination(natDestination string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat destination rule-set "+natDestination) - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatDestinationData(d *schema.ResourceData, natDestinationOptions natDestinationOptions) { - if tfErr := d.Set("name", natDestinationOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("from", natDestinationOptions.from); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("rule", natDestinationOptions.rule); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("description", natDestinationOptions.description); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/providersdk/resource_security_nat_destination_pool.go b/internal/providersdk/resource_security_nat_destination_pool.go deleted file mode 100644 index 68e08f8f..00000000 --- a/internal/providersdk/resource_security_nat_destination_pool.go +++ /dev/null @@ -1,374 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strconv" - "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" -) - -type natDestinationPoolOptions struct { - addressPort int - name string - address string - addressTo string - description string - routingInstance string -} - -func resourceSecurityNatDestinationPool() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatDestinationPoolCreate, - ReadWithoutTimeout: resourceSecurityNatDestinationPoolRead, - UpdateWithoutTimeout: resourceSecurityNatDestinationPoolUpdate, - DeleteWithoutTimeout: resourceSecurityNatDestinationPoolDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatDestinationPoolImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "address": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validateIPMaskFunc(), - }, - "address_port": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - ConflictsWith: []string{"address_to"}, - }, - "address_to": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateIPMaskFunc(), - ConflictsWith: []string{"address_port"}, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - } -} - -func resourceSecurityNatDestinationPoolCreate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatDestinationPool(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 !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat destination pool not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - securityNatDestinationPoolExists, err := checkSecurityNatDestinationPoolExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatDestinationPoolExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("security nat destination pool %v already exists", d.Get("name").(string)))...) - } - - if err := setSecurityNatDestinationPool(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_destination_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - securityNatDestinationPoolExists, err = checkSecurityNatDestinationPoolExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatDestinationPoolExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat destination pool %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourceSecurityNatDestinationPoolReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatDestinationPoolRead(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 resourceSecurityNatDestinationPoolReadWJunSess(d, junSess) -} - -func resourceSecurityNatDestinationPoolReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natDestinationPoolOptions, err := readSecurityNatDestinationPool(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natDestinationPoolOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatDestinationPoolData(d, natDestinationPoolOptions) - } - - return nil -} - -func resourceSecurityNatDestinationPoolUpdate(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 := delSecurityNatDestinationPool(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setSecurityNatDestinationPool(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 := delSecurityNatDestinationPool(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setSecurityNatDestinationPool(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_destination_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatDestinationPoolReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatDestinationPoolDelete(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatDestinationPool(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 := delSecurityNatDestinationPool(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_destination_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatDestinationPoolImport(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) - - securityNatDestinationPoolExists, err := checkSecurityNatDestinationPoolExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !securityNatDestinationPoolExists { - return nil, fmt.Errorf("don't find nat destination pool with id '%v' (id must be )", d.Id()) - } - natDestinationPoolOptions, err := readSecurityNatDestinationPool(d.Id(), junSess) - if err != nil { - return nil, err - } - fillSecurityNatDestinationPoolData(d, natDestinationPoolOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatDestinationPoolExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat destination pool " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatDestinationPool(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - setPrefix := "set security nat destination pool " + d.Get("name").(string) - configSet = append(configSet, setPrefix+" address "+d.Get("address").(string)) - if d.Get("address_port").(int) != 0 { - configSet = append(configSet, setPrefix+" address port "+strconv.Itoa(d.Get("address_port").(int))) - } - if d.Get("address_to").(string) != "" { - configSet = append(configSet, setPrefix+" address to "+d.Get("address_to").(string)) - } - if v := d.Get("description").(string); v != "" { - configSet = append(configSet, setPrefix+" description \""+v+"\"") - } - if d.Get("routing_instance").(string) != "" { - configSet = append(configSet, setPrefix+" routing-instance "+d.Get("routing_instance").(string)) - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatDestinationPool(name string, junSess *junos.Session, -) (confRead natDestinationPoolOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat destination pool " + 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, "address port "): - confRead.addressPort, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "address to "): - confRead.addressTo = itemTrim - case balt.CutPrefixInString(&itemTrim, "address "): - confRead.address = itemTrim - case balt.CutPrefixInString(&itemTrim, "description "): - confRead.description = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "routing-instance "): - confRead.routingInstance = itemTrim - } - } - } - - return confRead, nil -} - -func delSecurityNatDestinationPool(natDestinationPool string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat destination pool "+natDestinationPool) - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatDestinationPoolData(d *schema.ResourceData, natDestinationPoolOptions natDestinationPoolOptions) { - if tfErr := d.Set("name", natDestinationPoolOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("address", natDestinationPoolOptions.address); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("address_port", natDestinationPoolOptions.addressPort); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("address_to", natDestinationPoolOptions.addressTo); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("description", natDestinationPoolOptions.description); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", natDestinationPoolOptions.routingInstance); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/tfvalidator/string_net_test.go b/internal/tfvalidator/string_net_test.go index 48620f25..53353bcf 100644 --- a/internal/tfvalidator/string_net_test.go +++ b/internal/tfvalidator/string_net_test.go @@ -37,6 +37,10 @@ func TestStringIPAddress(t *testing.T) { val: types.StringValue("2001:2::1"), expectError: false, }, + "empty": { + val: types.StringValue(""), + expectError: true, + }, "invalid": { val: types.StringValue("292.0.2.1"), expectError: true, @@ -220,6 +224,10 @@ func TestStringCIDRNetwork(t *testing.T) { val: types.StringValue("192.0.2.0/24"), expectError: false, }, + "empty": { + val: types.StringValue(""), + expectError: true, + }, "invalid": { val: types.StringValue("192.0.2.1/24"), expectError: true, From 0a471f0bdb97dd976360b1486803ecf7abacfe04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Aug 2023 00:13:26 +0000 Subject: [PATCH 04/48] deps: bump github.com/hashicorp/terraform-plugin-framework Bumps [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) from 1.3.3 to 1.3.4. - [Release notes](https://github.com/hashicorp/terraform-plugin-framework/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-framework/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-framework/compare/v1.3.3...v1.3.4) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-framework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6e272d83..8d535d97 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-framework v1.3.3 + github.com/hashicorp/terraform-plugin-framework v1.3.4 github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-mux v0.11.2 diff --git a/go.sum b/go.sum index 5cffa446..0f3c7086 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980= github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA= github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= -github.com/hashicorp/terraform-plugin-framework v1.3.3 h1:D18BlA8gdV4+W8WKhUqxudiYomPZHv94FFzyoSCKC8Q= -github.com/hashicorp/terraform-plugin-framework v1.3.3/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= +github.com/hashicorp/terraform-plugin-framework v1.3.4 h1:dOTLsALgmQu+PawAvhfGQ04H0MeIz3EZmBw7OFvj7qs= +github.com/hashicorp/terraform-plugin-framework v1.3.4/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 h1:4L0tmy/8esP6OcvocVymw52lY0HyQ5OxB7VNl7k4bS0= github.com/hashicorp/terraform-plugin-framework-validators v0.10.0/go.mod h1:qdQJCdimB9JeX2YwOpItEu+IrfoJjWQ5PhLpAOMDQAE= github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= From 2981be8ee3b2e5f37a3d354467f7637e3e9b760d Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 7 Aug 2023 09:02:31 +0200 Subject: [PATCH 05/48] r/security_nat_source (& _pool): use new provider via framework --- .changes/security-nat-with-fwk.md | 6 + docs/resources/security_nat_source.md | 38 +- docs/resources/security_nat_source_pool.md | 16 +- internal/providerfwk/provider.go | 2 + .../resource_security_nat_source.go | 915 ++++++++++++++++++ .../resource_security_nat_source_pool.go | 604 ++++++++++++ .../resource_security_nat_source_test.go | 44 +- .../upgradestate_security_nat_source.go | 167 ++++ .../upgradestate_security_nat_source_test.go | 70 ++ internal/providersdk/func_common.go | 25 - internal/providersdk/provider.go | 2 - .../resource_security_nat_source.go | 638 ------------ .../resource_security_nat_source_pool.go | 518 ---------- internal/tfvalidator/string_net_test.go | 4 + 14 files changed, 1810 insertions(+), 1239 deletions(-) create mode 100644 internal/providerfwk/resource_security_nat_source.go create mode 100644 internal/providerfwk/resource_security_nat_source_pool.go rename internal/{providersdk => providerfwk}/resource_security_nat_source_test.go (83%) create mode 100644 internal/providerfwk/upgradestate_security_nat_source.go create mode 100644 internal/providerfwk/upgradestate_security_nat_source_test.go delete mode 100644 internal/providersdk/resource_security_nat_source.go delete mode 100644 internal/providersdk/resource_security_nat_source_pool.go diff --git a/.changes/security-nat-with-fwk.md b/.changes/security-nat-with-fwk.md index ee374033..e7871a08 100644 --- a/.changes/security-nat-with-fwk.md +++ b/.changes/security-nat-with-fwk.md @@ -7,3 +7,9 @@ ENHANCEMENTS: the resource schema has been upgraded to have one-blocks in single mode instead of list * **resource/junos_security_nat_destination_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional string attributes doesn't accept *empty* value +* **resource/junos_security_nat_source**: 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 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_security_nat_source_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional string attributes doesn't accept *empty* value diff --git a/docs/resources/security_nat_source.md b/docs/resources/security_nat_source.md index 88015716..d432c6df 100644 --- a/docs/resources/security_nat_source.md +++ b/docs/resources/security_nat_source.md @@ -38,52 +38,52 @@ resource "junos_security_nat_source" "demo_snat" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of source nat. + Source nat rule-set name. - **from** (Required, Block) - Declare `from` configuration. + Declare where is the traffic from. - **type** (Required, String) - Type of from options. + Type of traffic source. Need to be `interface`, `routing-instance` or `zone`. - **value** (Required, Set of String) - Name of interface, routing-instance or zone for from options. + Name of interface, routing-instance or zone for traffic source. - **to** (Required, Block) - Declare `to` configuration. + Declare where is the traffic to. - **type** (Required, String) - Type of to options. + Type of traffic destination. Need to be `interface`, `routing-instance` or `zone`. - **value** (Required, Set of String) - Name of interface, routing-instance or zone for to options. + Name of interface, routing-instance or zone for traffic destination. - **rule** (Required, Block List) - For each name of rule to declare. + For each name of source nat rule to declare. See [below for nested schema](#rule-arguments). - **description** (Optional, String) - Text description of rule set + Text description of rule set. --- ### rule arguments - **name** (Required, String) - Name of rule. + Rule name. - **match** (Required, Block) - Declare `match` configuration. + Specify source nat rule match criteria. - **application** (Optional, Set of String) - Specify application or application-set name to match. + Application or application-set name to match. - **destination_address** (Optional, Set of String) - List of CIDR destination address to match. + CIDR destination address to match. - **destination_address_name** (Optional, Set of String) - List of destination address from address book to match. + Destination address from address book to match. - **destination_port** (Optional, Set of String) - List of destination port to match. + Destination port to match. Format need to be `x` or `x to y`. - **protocol** (Optional, Set of String) - List of protocol to match. + IP Protocol to match. - **source_address** (Optional, Set of String) - List of CIDR source address to match. + CIDR source address to match. - **source_address_name** (Optional, Set of String) - List of source address from address book to match. + Source address from address book to match. - **source_port** (Optional, Set of String) - List of source port to match. + Source port to match. Format need to be `x` or `x to y`. - **then** (Required, Block) Declare `then` configuration. diff --git a/docs/resources/security_nat_source_pool.md b/docs/resources/security_nat_source_pool.md index df0d76bf..62c732fb 100644 --- a/docs/resources/security_nat_source_pool.md +++ b/docs/resources/security_nat_source_pool.md @@ -21,29 +21,29 @@ resource "junos_security_nat_source_pool" "demo_snat_pool" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of source nat pool. + Pool name. - **address** (Required, List of String) - List of CIDR for source nat pool. + CIDR address to source nat pool. - **address_pooling** (Optional, String) Type of address pooling. Need to be `paired` or `no-paired`. - **description** (Optional, String) - Text description of pool -- **pool_utilization_alarm_raise_threshold** (Optional, Number) - Upper threshold at which an SNMP trap is triggered. - Range 50 through 100. + Text description of pool. - **pool_utilization_alarm_clear_threshold** (Optional, Number) Lower threshold at which an SNMP trap is triggered. Range 40 through 100. +- **pool_utilization_alarm_raise_threshold** (Optional, Number) + Upper threshold at which an SNMP trap is triggered. + Range 50 through 100. - **port_no_translation** (Optional, Boolean) Do not perform port translation. - **port_overloading_factor** (Optional, Number) Port overloading factor for each IP. - **port_range** (Optional, String) - Range of port for source nat. + Range of port to source nat. Format need to match `\d+(-\d+)?` with minimum low port 1024 and maximum high port 65535. - **routing_instance** (Optional, String) - Name of routing instance for switch with nat. + Name of routing instance to switch instance with nat. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 754e6cd7..6a2eb123 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -229,6 +229,8 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newSecurityIpsecVpnResource, newSecurityNatDestinationResource, newSecurityNatDestinationPoolResource, + newSecurityNatSourceResource, + newSecurityNatSourcePoolResource, newSecurityPolicyResource, newSecurityPolicyTunnelPairPolicyResource, newSecurityZoneResource, diff --git a/internal/providerfwk/resource_security_nat_source.go b/internal/providerfwk/resource_security_nat_source.go new file mode 100644 index 00000000..ce409e11 --- /dev/null +++ b/internal/providerfwk/resource_security_nat_source.go @@ -0,0 +1,915 @@ +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/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 = &securityNatSource{} + _ resource.ResourceWithConfigure = &securityNatSource{} + _ resource.ResourceWithValidateConfig = &securityNatSource{} + _ resource.ResourceWithImportState = &securityNatSource{} + _ resource.ResourceWithUpgradeState = &securityNatSource{} +) + +type securityNatSource struct { + client *junos.Client +} + +func newSecurityNatSourceResource() resource.Resource { + return &securityNatSource{} +} + +func (rsc *securityNatSource) typeName() string { + return providerName + "_security_nat_source" +} + +func (rsc *securityNatSource) junosName() string { + return "security nat source rule-set" +} + +func (rsc *securityNatSource) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatSource) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatSource) 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 *securityNatSource) 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: "Source nat rule-set name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description of rule set.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.SingleNestedBlock{ + Description: "Declare where is the traffic from.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of traffice source.", + Validators: []validator.String{ + stringvalidator.OneOf("interface", "routing-instance", "zone"), + }, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "Name of interface, routing-instance or zone for traffic source.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + ), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "to": schema.SingleNestedBlock{ + Description: "Declare where is the traffic to.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of traffic destination.", + Validators: []validator.String{ + stringvalidator.OneOf("interface", "routing-instance", "zone"), + }, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "Name of interface, routing-instance or zone for traffic destination.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + ), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "rule": schema.ListNestedBlock{ + Description: "For each name of source nat rule to declare.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Rule name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + Blocks: map[string]schema.Block{ + "match": schema.SingleNestedBlock{ + Description: "Specify source nat rule match criteria.", + Attributes: map[string]schema.Attribute{ + "application": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Application or application-set name to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "destination_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "CIDR destination address to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "destination_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Destination address from address book to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + }, + }, + "destination_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Destination port to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d+( to \d+)?$`), + "must be use format `x` or `x to y`", + ), + ), + }, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "IP Protocol to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "CIDR source address to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source address from address book to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + }, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source port to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d+( to \d+)?$`), + "must be use format `x` or `x to y`", + ), + ), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "then": schema.SingleNestedBlock{ + Description: "Declare `then` action.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of source nat.", + Validators: []validator.String{ + stringvalidator.OneOf("interface", "off", "pool"), + }, + }, + "pool": schema.StringAttribute{ + Optional: true, + Description: "Name of source nat pool when type is pool.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + }, + } +} + +type securityNatSourceData struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatSourceBlockFromTo `tfsdk:"from"` + To *securityNatSourceBlockFromTo `tfsdk:"to"` + Rule []securityNatSourceBlockRule `tfsdk:"rule"` +} + +type securityNatSourceConfig struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatSourceBlockFromToConfig `tfsdk:"from"` + To *securityNatSourceBlockFromToConfig `tfsdk:"to"` + Rule types.List `tfsdk:"rule"` +} + +type securityNatSourceBlockFromTo struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` +} + +type securityNatSourceBlockFromToConfig struct { + Type types.String `tfsdk:"type"` + Value types.Set `tfsdk:"value"` +} + +type securityNatSourceBlockRule struct { + Name types.String `tfsdk:"name"` + Match *securityNatSourceBlockRuleBlockMatch `tfsdk:"match"` + Then *securityNatSourceBlockRuleBlockThen `tfsdk:"then"` +} + +type securityNatSourceBlockRuleConfig struct { + Name types.String `tfsdk:"name"` + Match *securityNatSourceBlockRuleBlockMatchConfig `tfsdk:"match"` + Then *securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` +} + +type securityNatSourceBlockRuleBlockMatch struct { + Application []types.String `tfsdk:"application"` + DestinationAddress []types.String `tfsdk:"destination_address"` + DestinationAddressName []types.String `tfsdk:"destination_address_name"` + DestinationPort []types.String `tfsdk:"destination_port"` + Protocol []types.String `tfsdk:"protocol"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` +} + +func (block *securityNatSourceBlockRuleBlockMatch) isEmpty() bool { + switch { + case len(block.Application) != 0: + return false + case len(block.DestinationAddress) != 0: + return false + case len(block.DestinationAddressName) != 0: + return false + case len(block.DestinationPort) != 0: + return false + case len(block.Protocol) != 0: + return false + case len(block.SourceAddress) != 0: + return false + case len(block.SourceAddressName) != 0: + return false + case len(block.SourcePort) != 0: + return false + default: + return true + } +} + +type securityNatSourceBlockRuleBlockMatchConfig struct { + Application types.Set `tfsdk:"application"` + DestinationAddress types.Set `tfsdk:"destination_address"` + DestinationAddressName types.Set `tfsdk:"destination_address_name"` + DestinationPort types.Set `tfsdk:"destination_port"` + Protocol types.Set `tfsdk:"protocol"` + SourceAddress types.Set `tfsdk:"source_address"` + SourceAddressName types.Set `tfsdk:"source_address_name"` + SourcePort types.Set `tfsdk:"source_port"` +} + +func (block *securityNatSourceBlockRuleBlockMatchConfig) isEmpty() bool { + switch { + case !block.Application.IsNull(): + return false + case !block.DestinationAddress.IsNull(): + return false + case !block.DestinationAddressName.IsNull(): + return false + case !block.DestinationPort.IsNull(): + return false + case !block.Protocol.IsNull(): + return false + case !block.SourceAddress.IsNull(): + return false + case !block.SourceAddressName.IsNull(): + return false + case !block.SourcePort.IsNull(): + return false + default: + return true + } +} + +type securityNatSourceBlockRuleBlockThen struct { + Type types.String `tfsdk:"type"` + Pool types.String `tfsdk:"pool"` +} + +func (rsc *securityNatSource) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatSourceConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.Rule.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule"), + tfdiag.MissingConfigErrSummary, + "at least one rule block must be specified", + ) + } else if !config.Rule.IsUnknown() { + var rule []securityNatSourceBlockRuleConfig + asDiags := config.Rule.ElementsAs(ctx, &rule, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + ruleName := make(map[string]struct{}) + for i, block := range rule { + if !block.Name.IsUnknown() { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("name"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple rule blocks with the same name %q", name), + ) + } + ruleName[name] = struct{}{} + } + + if block.Match == nil { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("match"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("match block must be specified"+ + " in rule block %q", block.Name.ValueString()), + ) + } else if block.Match.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("match"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("match block is empty"+ + " in rule block %q", block.Name.ValueString()), + ) + if block.Match.DestinationAddress.IsNull() && + block.Match.DestinationAddressName.IsNull() && + block.Match.SourceAddress.IsNull() && + block.Match.SourceAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("match"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("one of destination_address, destination_address_name, "+ + "source_address or source_address_name must be specified"+ + " in rule block %q", block.Name.ValueString()), + ) + } + } + if block.Then != nil { + if !block.Then.Type.IsUnknown() && !block.Then.Pool.IsUnknown() { + if block.Then.Type.ValueString() == "pool" { + if block.Then.Pool.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("missing pool value to source-nat pool"+ + " in rule block %q", block.Name.ValueString()), + ) + } + } + } + } + } + } +} + +func (rsc *securityNatSource) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatSourceData + 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 { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + natSourceExists, err := checkSecurityNatSourceExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if natSourceExists { + 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 { + natSourceExists, err := checkSecurityNatSourceExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !natSourceExists { + 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 *securityNatSource) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatSourceData + 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 *securityNatSource) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatSourceData + 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 *securityNatSource) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatSourceData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatSource) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data securityNatSourceData + + 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 checkSecurityNatSourceExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat source rule-set " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatSourceData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *securityNatSourceData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatSourceData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set security nat source rule-set " + rscData.Name.ValueString() + " " + + regexpPort := regexp.MustCompile(`^\d+( to \d+)?$`) + + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } + if rscData.From != nil { + for _, v := range rscData.From.Value { + configSet = append(configSet, setPrefix+"from "+rscData.From.Type.ValueString()+" "+v.ValueString()) + } + } + if rscData.To != nil { + for _, v := range rscData.To.Value { + configSet = append(configSet, setPrefix+"to "+rscData.To.Type.ValueString()+" "+v.ValueString()) + } + } + + ruleName := make(map[string]struct{}) + for i, block := range rscData.Rule { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + return path.Root("rule").AtListIndex(i).AtName("name"), + fmt.Errorf("multiple rule blocks with the same name %q", name) + } + ruleName[name] = struct{}{} + + setPrefixRule := setPrefix + " rule " + name + " " + + if block.Match != nil { + if block.Match.isEmpty() { + return path.Root("rule").AtListIndex(i).AtName("match"), + fmt.Errorf("match block is empty in rule block %q", block.Name.ValueString()) + } + if len(block.Match.DestinationAddress) == 0 && + len(block.Match.DestinationAddressName) == 0 && + len(block.Match.SourceAddress) == 0 && + len(block.Match.SourceAddressName) == 0 { + return path.Root("rule").AtListIndex(i).AtName("match"), + fmt.Errorf("one of destination_address, destination_address_name,"+ + " source_address or source_address_name arguments must be specified"+ + " in rule block %q", block.Name.ValueString()) + } + for _, v := range block.Match.Application { + configSet = append(configSet, setPrefixRule+"match application \""+v.ValueString()+"\"") + } + for _, v := range block.Match.DestinationAddress { + configSet = append(configSet, setPrefixRule+"match destination-address "+v.ValueString()) + } + for _, v := range block.Match.DestinationAddressName { + configSet = append(configSet, setPrefixRule+"match destination-address-name \""+v.ValueString()+"\"") + } + for _, v := range block.Match.DestinationPort { + if !regexpPort.MatchString(v.ValueString()) { + return path.Root("rule").AtListIndex(i).AtName("destination_port"), + fmt.Errorf("destination_port must be use format `x` or `x to y`"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"match destination-port "+v.ValueString()) + } + for _, v := range block.Match.Protocol { + configSet = append(configSet, setPrefixRule+"match protocol "+v.ValueString()) + } + for _, v := range block.Match.SourceAddress { + configSet = append(configSet, setPrefixRule+"match source-address "+v.ValueString()) + } + for _, v := range block.Match.SourceAddressName { + configSet = append(configSet, setPrefixRule+"match source-address-name \""+v.ValueString()+"\"") + } + for _, v := range block.Match.SourcePort { + if !regexpPort.MatchString(v.ValueString()) { + return path.Root("rule").AtListIndex(i).AtName("destination_port"), + fmt.Errorf("source_port must be use format `x` or `x to y`"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"match source-port "+v.ValueString()) + } + } else { + return path.Root("rule").AtListIndex(i).AtName("match"), + fmt.Errorf("match block must be specified"+ + " in rule block %q", block.Name.ValueString()) + } + if block.Then != nil { + switch thenType := block.Then.Type.ValueString(); thenType { + case "interface", "off": + configSet = append(configSet, setPrefixRule+"then source-nat "+thenType) + case "pool": + if block.Then.Pool.ValueString() == "" { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("pool"), + fmt.Errorf("missing pool value to source-nat pool"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"then source-nat "+thenType+" "+block.Then.Pool.ValueString()) + } + } + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatSourceData) read( + _ context.Context, name string, junSess *junos.Session, +) error { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat source rule-set " + 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, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "from "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { // + return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) + } + if rscData.From == nil { + rscData.From = &securityNatSourceBlockFromTo{ + Type: types.StringValue(itemTrimFields[0]), + } + } + rscData.From.Value = append(rscData.From.Value, types.StringValue(itemTrimFields[1])) + case balt.CutPrefixInString(&itemTrim, "to "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { // + return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "to", itemTrim) + } + if rscData.To == nil { + rscData.To = &securityNatSourceBlockFromTo{ + Type: types.StringValue(itemTrimFields[0]), + } + } + rscData.To.Value = append(rscData.To.Value, types.StringValue(itemTrimFields[1])) + case balt.CutPrefixInString(&itemTrim, "rule "): + itemTrimFields := strings.Split(itemTrim, " ") + var rule securityNatSourceBlockRule + rscData.Rule, rule = tfdata.ExtractBlockWithTFTypesString( + rscData.Rule, "Name", itemTrimFields[0], + ) + rule.Name = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + switch { + case balt.CutPrefixInString(&itemTrim, "match "): + if rule.Match == nil { + rule.Match = &securityNatSourceBlockRuleBlockMatch{} + } + switch { + case balt.CutPrefixInString(&itemTrim, "application "): + rule.Match.Application = append(rule.Match.Application, + types.StringValue(strings.Trim(itemTrim, "\"")), + ) + case balt.CutPrefixInString(&itemTrim, "destination-address "): + rule.Match.DestinationAddress = append(rule.Match.DestinationAddress, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "destination-address-name "): + rule.Match.DestinationAddressName = append(rule.Match.DestinationAddressName, + types.StringValue(strings.Trim(itemTrim, "\"")), + ) + case balt.CutPrefixInString(&itemTrim, "destination-port "): + rule.Match.DestinationPort = append(rule.Match.DestinationPort, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "protocol "): + rule.Match.Protocol = append(rule.Match.Protocol, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "source-address "): + rule.Match.SourceAddress = append(rule.Match.SourceAddress, + types.StringValue(itemTrim), + ) + case balt.CutPrefixInString(&itemTrim, "source-address-name "): + rule.Match.SourceAddressName = append(rule.Match.SourceAddressName, + types.StringValue(strings.Trim(itemTrim, "\"")), + ) + case balt.CutPrefixInString(&itemTrim, "source-port "): + rule.Match.SourcePort = append(rule.Match.SourcePort, + types.StringValue(itemTrim), + ) + } + case balt.CutPrefixInString(&itemTrim, "then source-nat "): + if rule.Then == nil { + rule.Then = &securityNatSourceBlockRuleBlockThen{} + } + if balt.CutPrefixInString(&itemTrim, "pool ") { + rule.Then.Type = types.StringValue("pool") + rule.Then.Pool = types.StringValue(itemTrim) + } else { + rule.Then.Type = types.StringValue(itemTrim) + } + } + rscData.Rule = append(rscData.Rule, rule) + } + } + } + + return nil +} + +func (rscData *securityNatSourceData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat source rule-set " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providerfwk/resource_security_nat_source_pool.go b/internal/providerfwk/resource_security_nat_source_pool.go new file mode 100644 index 00000000..50ce4d46 --- /dev/null +++ b/internal/providerfwk/resource_security_nat_source_pool.go @@ -0,0 +1,604 @@ +package providerfwk + +import ( + "context" + "fmt" + "regexp" + "strconv" + "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/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/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 = &securityNatSourcePool{} + _ resource.ResourceWithConfigure = &securityNatSourcePool{} + _ resource.ResourceWithValidateConfig = &securityNatSourcePool{} + _ resource.ResourceWithImportState = &securityNatSourcePool{} +) + +type securityNatSourcePool struct { + client *junos.Client +} + +func newSecurityNatSourcePoolResource() resource.Resource { + return &securityNatSourcePool{} +} + +func (rsc *securityNatSourcePool) typeName() string { + return providerName + "_security_nat_source_pool" +} + +func (rsc *securityNatSourcePool) junosName() string { + return "security nat source pool" +} + +func (rsc *securityNatSourcePool) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatSourcePool) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatSourcePool) 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 *securityNatSourcePool) 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: "Pool name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "address": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + Description: "CIDR address to source nat pool.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + tfvalidator.StringCIDR(), + ), + }, + }, + "address_pooling": schema.StringAttribute{ + Optional: true, + Description: "Type of address pooling.", + Validators: []validator.String{ + stringvalidator.OneOf("no-paired", "paired"), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description of pool.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "pool_utilization_alarm_clear_threshold": schema.Int64Attribute{ + Optional: true, + Description: "Lower threshold at which an SNMP trap is triggered.", + Validators: []validator.Int64{ + int64validator.Between(40, 100), + }, + }, + "pool_utilization_alarm_raise_threshold": schema.Int64Attribute{ + Optional: true, + Description: "Upper threshold at which an SNMP trap is triggered.", + Validators: []validator.Int64{ + int64validator.Between(50, 100), + }, + }, + "port_no_translation": schema.BoolAttribute{ + Optional: true, + Description: "Do not perform port translation.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "port_overloading_factor": schema.Int64Attribute{ + Optional: true, + Description: "Port overloading factor for each IP.", + Validators: []validator.Int64{ + int64validator.Between(2, 32), + }, + }, + "port_range": schema.StringAttribute{ + Optional: true, + Description: "Range of port to source nat.", + Validators: []validator.String{ + attributeSecurityNatSourcePoolPortRangeValidator{}, + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Name of routing instance to switch instance with nat.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + } +} + +type attributeSecurityNatSourcePoolPortRangeValidator struct{} + +func (v attributeSecurityNatSourcePoolPortRangeValidator) Description(_ context.Context) string { + return "Must be a valid Security Nat Source Pool Port Range." +} + +func (v attributeSecurityNatSourcePoolPortRangeValidator) MarkdownDescription(_ context.Context) string { + return "Must be a valid Security Nat Source Pool Port Range." +} + +func (v attributeSecurityNatSourcePoolPortRangeValidator) ValidateString( + _ context.Context, req validator.StringRequest, resp *validator.StringResponse, +) { + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + value := req.ConfigValue.ValueString() + if ok := regexp.MustCompile(`^\d+(-\d+)?$`).MatchString(value); !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + fmt.Sprintf(`expected value of port_range to match regular expression \d+(-\d+)?, got %v`, v), + ) + + return + } + vSplit := strings.Split(value, "-") + low, err := strconv.Atoi(vSplit[0]) + if err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + err.Error(), + ) + + return + } + high := low + if len(vSplit) > 1 { + high, err = strconv.Atoi(vSplit[1]) + if err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + err.Error(), + ) + + return + } + } + if low > high { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + fmt.Sprintf("low(%d) in %s bigger than high(%d)", low, value, high), + ) + } + if low < 1024 { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + fmt.Sprintf("low(%d) in %s is too small (min 1024)", low, v), + ) + } + if high > 65535 { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Security Nat Source Pool Port Range", + fmt.Sprintf("high(%d) in %s is too big (max 65535)", high, v), + ) + } +} + +type securityNatSourcePoolData struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Address []types.String `tfsdk:"address"` + AddressPooling types.String `tfsdk:"address_pooling"` + Description types.String `tfsdk:"description"` + PoolUtilizationAlarmClearThreshold types.Int64 `tfsdk:"pool_utilization_alarm_clear_threshold"` + PoolUtilizationAlarmRaiseThreshold types.Int64 `tfsdk:"pool_utilization_alarm_raise_threshold"` + PortNoTranslation types.Bool `tfsdk:"port_no_translation"` + PortOverloadingFactor types.Int64 `tfsdk:"port_overloading_factor"` + PortRange types.String `tfsdk:"port_range"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +type securityNatSourcePoolConfig struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Address types.List `tfsdk:"address"` + AddressPooling types.String `tfsdk:"address_pooling"` + Description types.String `tfsdk:"description"` + PoolUtilizationAlarmClearThreshold types.Int64 `tfsdk:"pool_utilization_alarm_clear_threshold"` + PoolUtilizationAlarmRaiseThreshold types.Int64 `tfsdk:"pool_utilization_alarm_raise_threshold"` + PortNoTranslation types.Bool `tfsdk:"port_no_translation"` + PortOverloadingFactor types.Int64 `tfsdk:"port_overloading_factor"` + PortRange types.String `tfsdk:"port_range"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +func (rsc *securityNatSourcePool) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatSourcePoolConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.PoolUtilizationAlarmClearThreshold.IsNull() && + config.PoolUtilizationAlarmRaiseThreshold.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("pool_utilization_alarm_clear_threshold"), + tfdiag.MissingConfigErrSummary, + "pool_utilization_alarm_raise_threshold must be specified with pool_utilization_alarm_clear_threshold", + ) + } + if !config.PortNoTranslation.IsNull() && + !config.PortOverloadingFactor.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("port_no_translation"), + tfdiag.ConflictConfigErrSummary, + "port_no_translation and port_overloading_factor cannot be configured together", + ) + } + if !config.PortNoTranslation.IsNull() && + !config.PortRange.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("port_no_translation"), + tfdiag.ConflictConfigErrSummary, + "port_no_translation and port_range cannot be configured together", + ) + } + if !config.PortOverloadingFactor.IsNull() && + !config.PortRange.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("port_overloading_factor"), + tfdiag.ConflictConfigErrSummary, + "port_overloading_factor and port_range cannot be configured together", + ) + } +} + +func (rsc *securityNatSourcePool) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatSourcePoolData + 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 { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + poolExists, err := checkSecurityNatSourcePoolExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if poolExists { + 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 { + poolExists, err := checkSecurityNatSourcePoolExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !poolExists { + 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 *securityNatSourcePool) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatSourcePoolData + 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 *securityNatSourcePool) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatSourcePoolData + 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 *securityNatSourcePool) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatSourcePoolData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatSourcePool) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data securityNatSourcePoolData + + 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 checkSecurityNatSourcePoolExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat source pool " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatSourcePoolData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *securityNatSourcePoolData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatSourcePoolData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set security nat source pool " + rscData.Name.ValueString() + " " + + for _, v := range rscData.Address { + configSet = append(configSet, setPrefix+"address "+v.ValueString()) + } + if v := rscData.AddressPooling.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"address-pooling "+v) + } + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } + if !rscData.PoolUtilizationAlarmClearThreshold.IsNull() { + configSet = append(configSet, setPrefix+"pool-utilization-alarm clear-threshold "+ + utils.ConvI64toa(rscData.PoolUtilizationAlarmClearThreshold.ValueInt64())) + } + if !rscData.PoolUtilizationAlarmRaiseThreshold.IsNull() { + configSet = append(configSet, setPrefix+"pool-utilization-alarm raise-threshold "+ + utils.ConvI64toa(rscData.PoolUtilizationAlarmRaiseThreshold.ValueInt64())) + } + if rscData.PortNoTranslation.ValueBool() { + configSet = append(configSet, setPrefix+"port no-translation") + } + if !rscData.PortOverloadingFactor.IsNull() { + configSet = append(configSet, setPrefix+"port port-overloading-factor "+ + utils.ConvI64toa(rscData.PortOverloadingFactor.ValueInt64())) + } + if v := rscData.PortRange.ValueString(); v != "" { + vSplit := strings.Split(v, "-") + if len(vSplit) > 1 { + configSet = append(configSet, setPrefix+"port range "+vSplit[0]+" to "+vSplit[1]) + } else { + configSet = append(configSet, setPrefix+"port range "+vSplit[0]) + } + } + if v := rscData.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"routing-instance "+v) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatSourcePoolData) read( + _ context.Context, name string, junSess *junos.Session, +) error { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat source pool " + 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, "address "): + rscData.Address = append(rscData.Address, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "address-pooling "): + rscData.AddressPooling = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "pool-utilization-alarm clear-threshold "): + rscData.PoolUtilizationAlarmClearThreshold, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "pool-utilization-alarm raise-threshold "): + rscData.PoolUtilizationAlarmRaiseThreshold, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "port no-translation": + rscData.PortNoTranslation = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "port port-overloading-factor "): + rscData.PortOverloadingFactor, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "port range to "): + rscData.PortRange = types.StringValue(rscData.PortRange.ValueString() + "-" + itemTrim) + case balt.CutPrefixInString(&itemTrim, "port range "): + rscData.PortRange = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "routing-instance "): + rscData.RoutingInstance = types.StringValue(itemTrim) + } + } + } + + return nil +} + +func (rscData *securityNatSourcePoolData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat source pool " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_security_nat_source_test.go b/internal/providerfwk/resource_security_nat_source_test.go similarity index 83% rename from internal/providersdk/resource_security_nat_source_test.go rename to internal/providerfwk/resource_security_nat_source_test.go index 3feedfc8..06c78f98 100644 --- a/internal/providersdk/resource_security_nat_source_test.go +++ b/internal/providerfwk/resource_security_nat_source_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 TestAccJunosSecurityNatSource_basic(t *testing.T) { @@ -17,41 +17,33 @@ func TestAccJunosSecurityNatSource_basic(t *testing.T) { Config: testAccJunosSecurityNatSourceConfigCreate(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "from.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "from.0.type", "zone"), + "from.type", "zone"), resource.TestCheckTypeSetElemAttr("junos_security_nat_source.testacc_securitySNAT", - "from.0.value.*", "testacc_securitySNAT"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "to.#", "1"), + "from.value.*", "testacc_securitySNAT"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "to.0.type", "zone"), + "to.type", "zone"), resource.TestCheckTypeSetElemAttr("junos_security_nat_source.testacc_securitySNAT", - "to.0.value.*", "testacc_securitySNAT"), + "to.value.*", "testacc_securitySNAT"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", "rule.#", "1"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", "rule.0.name", "testacc_securitySNATRule"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.source_address.#", "1"), + "rule.0.match.source_address.#", "1"), resource.TestCheckTypeSetElemAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.source_address.*", "192.0.2.0/25"), + "rule.0.match.source_address.*", "192.0.2.0/25"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.destination_address.#", "1"), + "rule.0.match.destination_address.#", "1"), resource.TestCheckTypeSetElemAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.destination_address.*", "192.0.2.128/25"), + "rule.0.match.destination_address.*", "192.0.2.128/25"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.protocol.#", "1"), + "rule.0.match.protocol.#", "1"), resource.TestCheckTypeSetElemAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.match.0.protocol.*", "tcp"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.then.#", "1"), + "rule.0.match.protocol.*", "tcp"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.then.0.type", "pool"), + "rule.0.then.type", "pool"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.0.then.0.pool", "testacc_securitySNATPool"), + "rule.0.then.pool", "testacc_securitySNATPool"), resource.TestCheckResourceAttr("junos_security_nat_source_pool.testacc_securitySNATPool", "address.#", "2"), resource.TestCheckResourceAttr("junos_security_nat_source_pool.testacc_securitySNATPool", @@ -76,15 +68,9 @@ func TestAccJunosSecurityNatSource_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", "rule.#", "3"), resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.1.match.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.1.then.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_source.testacc_securitySNAT", - "rule.1.then.0.type", "off"), + "rule.1.then.type", "off"), resource.TestCheckResourceAttr("junos_security_nat_source_pool.testacc_securitySNATPool", "address_pooling", "no-paired"), - resource.TestCheckResourceAttr("junos_security_nat_source_pool.testacc_securitySNATPool", - "port_no_translation", "false"), resource.TestCheckResourceAttr("junos_security_nat_source_pool.testacc_securitySNATPool", "port_overloading_factor", "3"), ), diff --git a/internal/providerfwk/upgradestate_security_nat_source.go b/internal/providerfwk/upgradestate_security_nat_source.go new file mode 100644 index 00000000..f94810e6 --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_source.go @@ -0,0 +1,167 @@ +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 *securityNatSource) 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, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + "to": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + "rule": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "match": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "application": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "destination_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "destination_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "destination_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + "then": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "pool": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeSecurityNatSourceV0toV1, + }, + } +} + +func upgradeSecurityNatSourceV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []securityNatSourceBlockFromTo `tfsdk:"from"` + To []securityNatSourceBlockFromTo `tfsdk:"to"` + Rule []struct { + Name types.String `tfsdk:"name"` + Match []securityNatSourceBlockRuleBlockMatch `tfsdk:"match"` + Then []securityNatSourceBlockRuleBlockThen `tfsdk:"then"` + } `tfsdk:"rule"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 securityNatSourceData + dataV1.ID = dataV0.ID + dataV1.Name = dataV0.Name + dataV1.Description = dataV0.Description + if len(dataV0.From) > 0 { + dataV1.From = &dataV0.From[0] + } + if len(dataV0.To) > 0 { + dataV1.To = &dataV0.To[0] + } + for _, blockV0 := range dataV0.Rule { + blockV1 := securityNatSourceBlockRule{ + Name: blockV0.Name, + } + if len(blockV0.Match) > 0 { + blockV1.Match = &blockV0.Match[0] + } + if len(blockV0.Then) > 0 { + blockV1.Then = &blockV0.Then[0] + } + dataV1.Rule = append(dataV1.Rule, blockV1) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_security_nat_source_test.go b/internal/providerfwk/upgradestate_security_nat_source_test.go new file mode 100644 index 00000000..e108514c --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_source_test.go @@ -0,0 +1,70 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosSecurityNatSourceUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_UPGRADE_STATE") == "" { + return + } + if os.Getenv("TESTACC_SRX") != "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosSecurityNatSourceConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosSecurityNatSourceConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosSecurityNatSourceConfigV0() string { + return ` +resource "junos_security_nat_source" "testacc_securitySNAT" { + name = "testacc_securitySNAT_upgrade" + description = "testacc securitySNAT upgrade" + from { + type = "zone" + value = [junos_security_zone.testacc_securitySNAT_upgrade.name] + } + to { + type = "zone" + value = [junos_security_zone.testacc_securitySNAT_upgrade.name] + } + rule { + name = "testacc_securitySNATRule" + match { + source_address = ["192.0.2.0/25"] + destination_address = ["192.0.2.128/25"] + protocol = ["tcp"] + } + then { + type = "off" + } + } +} +resource "junos_security_zone" "testacc_securitySNAT_upgrade" { + name = "testacc_securitySNAT_upgrade" +} +` +} diff --git a/internal/providersdk/func_common.go b/internal/providersdk/func_common.go index a578d067..d3261afa 100644 --- a/internal/providersdk/func_common.go +++ b/internal/providersdk/func_common.go @@ -90,31 +90,6 @@ func validateCIDRNetwork(network string) error { return nil } -func validateCIDRFunc() schema.SchemaValidateDiagFunc { - return func(i interface{}, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - v := i.(string) - if !strings.Contains(v, "/") { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("%v missing mask", v), - AttributePath: path, - }) - - return diags - } - if _, _, err := net.ParseCIDR(v); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("%v is not a valid CIDR", v), - AttributePath: path, - }) - } - - return diags - } -} - func validateNameObjectJunos(exclude []string, length int, format formatName) schema.SchemaValidateDiagFunc { return func(i interface{}, path cty.Path) diag.Diagnostics { var diags diag.Diagnostics diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 2c2241f4..25b82dd7 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -178,8 +178,6 @@ func Provider() *schema.Provider { "junos_security_idp_custom_attack_group": resourceSecurityIdpCustomAttackGroup(), "junos_security_idp_policy": resourceSecurityIdpPolicy(), "junos_security_log_stream": resourceSecurityLogStream(), - "junos_security_nat_source": resourceSecurityNatSource(), - "junos_security_nat_source_pool": resourceSecurityNatSourcePool(), "junos_security_nat_static": resourceSecurityNatStatic(), "junos_security_nat_static_rule": resourceSecurityNatStaticRule(), "junos_security_screen": resourceSecurityScreen(), diff --git a/internal/providersdk/resource_security_nat_source.go b/internal/providersdk/resource_security_nat_source.go deleted file mode 100644 index 1ae928d1..00000000 --- a/internal/providersdk/resource_security_nat_source.go +++ /dev/null @@ -1,638 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "regexp" - "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" - bchk "github.com/jeremmfr/go-utils/basiccheck" -) - -type natSourceOptions struct { - name string - description string - from []map[string]interface{} - to []map[string]interface{} - rule []map[string]interface{} -} - -func resourceSecurityNatSource() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatSourceCreate, - ReadWithoutTimeout: resourceSecurityNatSourceRead, - UpdateWithoutTimeout: resourceSecurityNatSourceUpdate, - DeleteWithoutTimeout: resourceSecurityNatSourceDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatSourceImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "from": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"interface", "routing-instance", "zone"}, false), - }, - "value": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "to": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"interface", "routing-instance", "zone"}, false), - }, - "value": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "rule": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "match": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "application": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "destination_address": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - "destination_address_name": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "destination_port": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "protocol": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_address": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - "source_address_name": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_port": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "then": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"interface", "pool", "off"}, false), - }, - "pool": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - }, - }, - }, - }, - }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourceSecurityNatSourceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatSource(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 !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat source not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - securityNatSourceExists, err := checkSecurityNatSourceExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatSourceExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat source %v already exists", d.Get("name").(string)))...) - } - - if err := setSecurityNatSource(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_source") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - securityNatSourceExists, err = checkSecurityNatSourceExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatSourceExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat source %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourceSecurityNatSourceReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatSourceRead(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 resourceSecurityNatSourceReadWJunSess(d, junSess) -} - -func resourceSecurityNatSourceReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natSourceOptions, err := readSecurityNatSource(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natSourceOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatSourceData(d, natSourceOptions) - } - - return nil -} - -func resourceSecurityNatSourceUpdate(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 := delSecurityNatSource(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setSecurityNatSource(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 := delSecurityNatSource(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setSecurityNatSource(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_source") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatSourceReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatSourceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatSource(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 := delSecurityNatSource(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_source") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatSourceImport(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) - - securityNatSourceExists, err := checkSecurityNatSourceExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !securityNatSourceExists { - return nil, fmt.Errorf("don't find nat source with id '%v' (id must be )", d.Id()) - } - natSourceOptions, err := readSecurityNatSource(d.Id(), junSess) - if err != nil { - return nil, err - } - fillSecurityNatSourceData(d, natSourceOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatSourceExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat source rule-set " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatSource(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - regexpPort := regexp.MustCompile(`^\d+( to \d+)?$`) - - setPrefix := "set security nat source rule-set " + d.Get("name").(string) - for _, v := range d.Get("from").([]interface{}) { - from := v.(map[string]interface{}) - for _, value := range sortSetOfString(from["value"].(*schema.Set).List()) { - configSet = append(configSet, setPrefix+" from "+from["type"].(string)+" "+value) - } - } - for _, v := range d.Get("to").([]interface{}) { - to := v.(map[string]interface{}) - for _, value := range sortSetOfString(to["value"].(*schema.Set).List()) { - configSet = append(configSet, setPrefix+" to "+to["type"].(string)+" "+value) - } - } - ruleNameList := make([]string, 0) - for _, v := range d.Get("rule").([]interface{}) { - rule := v.(map[string]interface{}) - if bchk.InSlice(rule["name"].(string), ruleNameList) { - return fmt.Errorf("multiple blocks rule with the same name %s", rule["name"].(string)) - } - ruleNameList = append(ruleNameList, rule["name"].(string)) - setPrefixRule := setPrefix + " rule " + rule["name"].(string) - for _, matchV := range rule["match"].([]interface{}) { - if matchV == nil { - return fmt.Errorf("match block in rule %s need to have an argument", rule["name"].(string)) - } - match := matchV.(map[string]interface{}) - if len(match["destination_address"].(*schema.Set).List()) == 0 && - len(match["destination_address_name"].(*schema.Set).List()) == 0 && - len(match["source_address"].(*schema.Set).List()) == 0 && - len(match["source_address_name"].(*schema.Set).List()) == 0 { - return fmt.Errorf("one of destination_address, destination_address_name, " + - "source_address or source_address_name arguments must be set") - } - for _, vv := range sortSetOfString(match["application"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match application \""+vv+"\"") - } - for _, address := range sortSetOfString(match["destination_address"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match destination-address "+address) - } - for _, vv := range sortSetOfString(match["destination_address_name"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match destination-address-name \""+vv+"\"") - } - for _, vv := range sortSetOfString(match["destination_port"].(*schema.Set).List()) { - if !regexpPort.MatchString(vv) { - return fmt.Errorf("destination_port need to have format `x` or `x to y` in rule %s", rule["name"].(string)) - } - configSet = append(configSet, setPrefixRule+" match destination-port "+vv) - } - for _, proto := range sortSetOfString(match["protocol"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match protocol "+proto) - } - for _, address := range sortSetOfString(match["source_address"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address "+address) - } - for _, vv := range sortSetOfString(match["source_address_name"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address-name \""+vv+"\"") - } - for _, vv := range sortSetOfString(match["source_port"].(*schema.Set).List()) { - if !regexpPort.MatchString(vv) { - return fmt.Errorf("source_port need to have format `x` or `x to y` in rule %s", rule["name"].(string)) - } - configSet = append(configSet, setPrefixRule+" match source-port "+vv) - } - } - for _, thenV := range rule["then"].([]interface{}) { - then := thenV.(map[string]interface{}) - if then["type"].(string) == "interface" { - configSet = append(configSet, setPrefixRule+" then source-nat interface") - } - if then["type"].(string) == "off" { - configSet = append(configSet, setPrefixRule+" then source-nat off") - } - if then["type"].(string) == "pool" { - if then["pool"].(string) == "" { - return fmt.Errorf("missing pool for source-nat pool for rule %v in %v", - rule["name"].(string), d.Get("name").(string)) - } - configSet = append(configSet, setPrefixRule+" then source-nat pool "+then["pool"].(string)) - } - } - } - if v := d.Get("description").(string); v != "" { - configSet = append(configSet, setPrefix+" description \""+v+"\"") - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatSource(name string, junSess *junos.Session, -) (confRead natSourceOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat source rule-set " + 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, "from "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { // - return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) - } - if len(confRead.from) == 0 { - confRead.from = append(confRead.from, map[string]interface{}{ - "type": itemTrimFields[0], - "value": make([]string, 0), - }) - } - confRead.from[0]["value"] = append(confRead.from[0]["value"].([]string), itemTrimFields[1]) - case balt.CutPrefixInString(&itemTrim, "to "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { // - return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "to", itemTrim) - } - if len(confRead.to) == 0 { - confRead.to = append(confRead.to, map[string]interface{}{ - "type": itemTrimFields[0], - "value": make([]string, 0), - }) - } - confRead.to[0]["value"] = append(confRead.to[0]["value"].([]string), itemTrimFields[1]) - case balt.CutPrefixInString(&itemTrim, "rule "): - itemTrimFields := strings.Split(itemTrim, " ") - ruleOptions := map[string]interface{}{ - "name": itemTrimFields[0], - "match": make([]map[string]interface{}, 0), - "then": make([]map[string]interface{}, 0), - } - confRead.rule = copyAndRemoveItemMapList("name", ruleOptions, confRead.rule) - switch { - case balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" match "): - if len(ruleOptions["match"].([]map[string]interface{})) == 0 { - ruleOptions["match"] = append(ruleOptions["match"].([]map[string]interface{}), - map[string]interface{}{ - "application": make([]string, 0), - "destination_address": make([]string, 0), - "destination_address_name": make([]string, 0), - "destination_port": make([]string, 0), - "protocol": make([]string, 0), - "source_address": make([]string, 0), - "source_address_name": make([]string, 0), - "source_port": make([]string, 0), - }) - } - ruleMatchOptions := ruleOptions["match"].([]map[string]interface{})[0] - switch { - case balt.CutPrefixInString(&itemTrim, "application "): - ruleMatchOptions["application"] = append( - ruleMatchOptions["application"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "destination-address "): - ruleMatchOptions["destination_address"] = append( - ruleMatchOptions["destination_address"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "destination-address-name "): - ruleMatchOptions["destination_address_name"] = append( - ruleMatchOptions["destination_address_name"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "destination-port "): - ruleMatchOptions["destination_port"] = append( - ruleMatchOptions["destination_port"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "protocol "): - ruleMatchOptions["protocol"] = append( - ruleMatchOptions["protocol"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "source-address "): - ruleMatchOptions["source_address"] = append( - ruleMatchOptions["source_address"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "source-address-name "): - ruleMatchOptions["source_address_name"] = append( - ruleMatchOptions["source_address_name"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "source-port "): - ruleMatchOptions["source_port"] = append( - ruleMatchOptions["source_port"].([]string), - itemTrim, - ) - } - case balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" then source-nat "): - if len(ruleOptions["then"].([]map[string]interface{})) == 0 { - ruleOptions["then"] = append(ruleOptions["then"].([]map[string]interface{}), - map[string]interface{}{ - "type": "", - "pool": "", - }) - } - ruleThenOptions := ruleOptions["then"].([]map[string]interface{})[0] - if balt.CutPrefixInString(&itemTrim, "pool ") { - ruleThenOptions["type"] = "pool" - ruleThenOptions["pool"] = itemTrim - } else { - ruleThenOptions["type"] = itemTrim - } - } - confRead.rule = append(confRead.rule, ruleOptions) - case balt.CutPrefixInString(&itemTrim, "description "): - confRead.description = strings.Trim(itemTrim, "\"") - } - } - } - - return confRead, nil -} - -func delSecurityNatSource(natSource string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat source rule-set "+natSource) - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatSourceData(d *schema.ResourceData, natSourceOptions natSourceOptions) { - if tfErr := d.Set("name", natSourceOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("from", natSourceOptions.from); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("to", natSourceOptions.to); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("rule", natSourceOptions.rule); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("description", natSourceOptions.description); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/providersdk/resource_security_nat_source_pool.go b/internal/providersdk/resource_security_nat_source_pool.go deleted file mode 100644 index 8231c9c3..00000000 --- a/internal/providersdk/resource_security_nat_source_pool.go +++ /dev/null @@ -1,518 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/go-cty/cty" - "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" -) - -type natSourcePoolOptions struct { - portNoTranslation bool - poolUtilizationAlarmClearThreshold int - poolUtilizationAlarmRaiseThreshold int - portOverloadingFactor int - addressPooling string - description string - name string - portRange string - routingInstance string - address []string -} - -func resourceSecurityNatSourcePool() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatSourcePoolCreate, - ReadWithoutTimeout: resourceSecurityNatSourcePoolRead, - UpdateWithoutTimeout: resourceSecurityNatSourcePoolUpdate, - DeleteWithoutTimeout: resourceSecurityNatSourcePoolDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatSourcePoolImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "address": { - Type: schema.TypeList, - Required: true, - MinItems: 1, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRFunc(), - }, - }, - "address_pooling": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"no-paired", "paired"}, false), - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - "pool_utilization_alarm_raise_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(50, 100), - }, - "pool_utilization_alarm_clear_threshold": { - Type: schema.TypeInt, - Optional: true, - RequiredWith: []string{"pool_utilization_alarm_raise_threshold"}, - ValidateFunc: validation.IntBetween(40, 100), - }, - "port_no_translation": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"port_overloading_factor", "port_range"}, - }, - "port_overloading_factor": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(2, 32), - ConflictsWith: []string{"port_no_translation", "port_range"}, - }, - "port_range": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"port_overloading_factor", "port_no_translation"}, - ValidateDiagFunc: validateSourcePoolPortRange(), - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - } -} - -func resourceSecurityNatSourcePoolCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatSourcePool(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 !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat source pool not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - securityNatSourcePoolExists, err := checkSecurityNatSourcePoolExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatSourcePoolExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("security nat source pool %v already exists", d.Get("name").(string)))...) - } - - if err := setSecurityNatSourcePool(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_source_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - securityNatSourcePoolExists, err = checkSecurityNatSourcePoolExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatSourcePoolExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat source pool %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourceSecurityNatSourcePoolReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatSourcePoolRead(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 resourceSecurityNatSourcePoolReadWJunSess(d, junSess) -} - -func resourceSecurityNatSourcePoolReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natSourcePoolOptions, err := readSecurityNatSourcePool(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natSourcePoolOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatSourcePoolData(d, natSourcePoolOptions) - } - - return nil -} - -func resourceSecurityNatSourcePoolUpdate(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 := delSecurityNatSourcePool(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setSecurityNatSourcePool(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 := delSecurityNatSourcePool(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setSecurityNatSourcePool(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_source_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatSourcePoolReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatSourcePoolDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatSourcePool(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 := delSecurityNatSourcePool(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_source_pool") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatSourcePoolImport(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) - - securityNatSourcePoolExists, err := checkSecurityNatSourcePoolExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !securityNatSourcePoolExists { - return nil, fmt.Errorf("don't find nat source pool with id '%v' (id must be )", d.Id()) - } - natSourcePoolOptions, err := readSecurityNatSourcePool(d.Id(), junSess) - if err != nil { - return nil, err - } - fillSecurityNatSourcePoolData(d, natSourcePoolOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatSourcePoolExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat source pool " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatSourcePool(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - setPrefix := "set security nat source pool " + d.Get("name").(string) - for _, v := range d.Get("address").([]interface{}) { - configSet = append(configSet, setPrefix+" address "+v.(string)) - } - if d.Get("address_pooling").(string) != "" { - configSet = append(configSet, setPrefix+" address-pooling "+d.Get("address_pooling").(string)) - } - if v := d.Get("description").(string); v != "" { - configSet = append(configSet, setPrefix+" description \""+v+"\"") - } - if d.Get("pool_utilization_alarm_clear_threshold").(int) != 0 { - configSet = append(configSet, setPrefix+" pool-utilization-alarm clear-threshold "+ - strconv.Itoa(d.Get("pool_utilization_alarm_clear_threshold").(int))) - } - if d.Get("pool_utilization_alarm_raise_threshold").(int) != 0 { - configSet = append(configSet, setPrefix+" pool-utilization-alarm raise-threshold "+ - strconv.Itoa(d.Get("pool_utilization_alarm_raise_threshold").(int))) - } - if d.Get("port_no_translation").(bool) { - configSet = append(configSet, setPrefix+" port no-translation ") - } - if d.Get("port_overloading_factor").(int) != 0 { - configSet = append(configSet, setPrefix+" port port-overloading-factor "+ - strconv.Itoa(d.Get("port_overloading_factor").(int))) - } - if d.Get("port_range").(string) != "" { - rangePort := strings.Split(d.Get("port_range").(string), "-") - if len(rangePort) > 1 { - configSet = append(configSet, setPrefix+" port range "+rangePort[0]+" to "+rangePort[1]) - } else { - configSet = append(configSet, setPrefix+" port range "+rangePort[0]) - } - } - if d.Get("routing_instance").(string) != "" { - configSet = append(configSet, setPrefix+" routing-instance "+d.Get("routing_instance").(string)) - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatSourcePool(name string, junSess *junos.Session, -) (confRead natSourcePoolOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat source pool " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - var portRange string - 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, "address "): - confRead.address = append(confRead.address, itemTrim) - case balt.CutPrefixInString(&itemTrim, "address-pooling "): - confRead.addressPooling = itemTrim - case balt.CutPrefixInString(&itemTrim, "description "): - confRead.description = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "pool-utilization-alarm clear-threshold "): - confRead.poolUtilizationAlarmClearThreshold, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "pool-utilization-alarm raise-threshold "): - confRead.poolUtilizationAlarmRaiseThreshold, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case itemTrim == "port no-translation": - confRead.portNoTranslation = true - case balt.CutPrefixInString(&itemTrim, "port port-overloading-factor "): - confRead.portOverloadingFactor, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "port range to "): - portRange += "-" + itemTrim - case balt.CutPrefixInString(&itemTrim, "port range "): - portRange = itemTrim - case balt.CutPrefixInString(&itemTrim, "routing-instance "): - confRead.routingInstance = itemTrim - } - } - confRead.portRange = portRange - } - - return confRead, nil -} - -func delSecurityNatSourcePool(natSourcePool string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat source pool "+natSourcePool) - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatSourcePoolData(d *schema.ResourceData, natSourcePoolOptions natSourcePoolOptions) { - if tfErr := d.Set("name", natSourcePoolOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("address", natSourcePoolOptions.address); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("address_pooling", natSourcePoolOptions.addressPooling); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("description", natSourcePoolOptions.description); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("pool_utilization_alarm_clear_threshold", - natSourcePoolOptions.poolUtilizationAlarmClearThreshold); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("pool_utilization_alarm_raise_threshold", - natSourcePoolOptions.poolUtilizationAlarmRaiseThreshold); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("port_no_translation", natSourcePoolOptions.portNoTranslation); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("port_overloading_factor", natSourcePoolOptions.portOverloadingFactor); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("port_range", natSourcePoolOptions.portRange); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", natSourcePoolOptions.routingInstance); tfErr != nil { - panic(tfErr) - } -} - -func validateSourcePoolPortRange() schema.SchemaValidateDiagFunc { - return func(i interface{}, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - v := i.(string) - if ok := regexp.MustCompile(`^\d+(-\d+)?$`).MatchString(v); !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf(`expected value of port_range to match regular expression \d+(-\d+)?, got %v`, v), - AttributePath: path, - }) - - return diags - } - vSplit := strings.Split(v, "-") - low, err := strconv.Atoi(vSplit[0]) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: err.Error(), - AttributePath: path, - }) - - return diags - } - high := low - if len(vSplit) > 1 { - high, err = strconv.Atoi(vSplit[1]) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: err.Error(), - AttributePath: path, - }) - - return diags - } - } - if low > high { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("low(%d) in %s bigger than high(%d)", low, v, high), - AttributePath: path, - }) - } - if low < 1024 { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("low(%d) in %s is too small (min 1024)", low, v), - AttributePath: path, - }) - } - if high > 65535 { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("high(%d) in %s is too big (max 65535)", high, v), - AttributePath: path, - }) - } - - return diags - } -} diff --git a/internal/tfvalidator/string_net_test.go b/internal/tfvalidator/string_net_test.go index 53353bcf..6003d4d8 100644 --- a/internal/tfvalidator/string_net_test.go +++ b/internal/tfvalidator/string_net_test.go @@ -130,6 +130,10 @@ func TestStringCIDR(t *testing.T) { val: types.StringValue("192.0.2.1/24"), expectError: false, }, + "empty": { + val: types.StringValue(""), + expectError: true, + }, "invalid": { val: types.StringValue("192.0.2.1"), expectError: true, From 3bac147838849a6fecc22a87b391b9b9d946ca4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 07:14:57 +0000 Subject: [PATCH 06/48] deps: bump github.com/hashicorp/terraform-plugin-framework-validators Bumps [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators) from 0.10.0 to 0.11.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-framework-validators/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-framework-validators/compare/v0.10.0...v0.11.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-framework-validators dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8d535d97..34c58f88 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-framework v1.3.4 - github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-mux v0.11.2 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 diff --git a/go.sum b/go.sum index 0f3c7086..015891e4 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQH github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= github.com/hashicorp/terraform-plugin-framework v1.3.4 h1:dOTLsALgmQu+PawAvhfGQ04H0MeIz3EZmBw7OFvj7qs= github.com/hashicorp/terraform-plugin-framework v1.3.4/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= -github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 h1:4L0tmy/8esP6OcvocVymw52lY0HyQ5OxB7VNl7k4bS0= -github.com/hashicorp/terraform-plugin-framework-validators v0.10.0/go.mod h1:qdQJCdimB9JeX2YwOpItEu+IrfoJjWQ5PhLpAOMDQAE= +github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 h1:DKb1bX7/EPZUTW6F5zdwJzS/EZ/ycVD6JAW5RYOj4f8= +github.com/hashicorp/terraform-plugin-framework-validators v0.11.0/go.mod h1:dzxOiHh7O9CAwc6p8N4mR1H++LtRkl+u+21YNiBVNno= github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= github.com/hashicorp/terraform-plugin-go v0.18.0/go.mod h1:l7VK+2u5Kf2y+A+742GX0ouLut3gttudmvMgN0PA74Y= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= From 0fcaf551aefdf02f69ac5f2638d87951b9103246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 00:42:58 +0000 Subject: [PATCH 07/48] deps: bump github.com/jeremmfr/go-netconf from 0.4.12 to 0.4.13 Bumps [github.com/jeremmfr/go-netconf](https://github.com/jeremmfr/go-netconf) from 0.4.12 to 0.4.13. - [Changelog](https://github.com/jeremmfr/go-netconf/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeremmfr/go-netconf/compare/v0.4.12...v0.4.13) --- updated-dependencies: - dependency-name: github.com/jeremmfr/go-netconf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 34c58f88..9a95919c 100644 --- a/go.mod +++ b/go.mod @@ -11,10 +11,10 @@ require ( github.com/hashicorp/terraform-plugin-mux v0.11.2 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 github.com/hashicorp/terraform-plugin-testing v1.4.0 - github.com/jeremmfr/go-netconf v0.4.12 + github.com/jeremmfr/go-netconf v0.4.13 github.com/jeremmfr/go-utils v0.9.0 github.com/jeremmfr/junosdecode v1.1.1 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 ) require ( @@ -55,8 +55,8 @@ require ( github.com/zclconf/go-cty v1.13.2 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.1 // indirect diff --git a/go.sum b/go.sum index 015891e4..47466a16 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jeremmfr/go-netconf v0.4.12 h1:Cb6GZUpVHCh2SHH0hytp4n3HIRKmNX3XlyZn56fUpV4= -github.com/jeremmfr/go-netconf v0.4.12/go.mod h1:x9OkucefrdLPCtn0H3jhnV8acsdnR3y7EorXTzOU+IA= +github.com/jeremmfr/go-netconf v0.4.13 h1:qOTE4I4GPfsjJeeVKNNM6zgXDumA1u9bNGMiEXEPQe0= +github.com/jeremmfr/go-netconf v0.4.13/go.mod h1:I0UUfijk7PhsvQqPMycuMmCx8K/z/aBZRyPzwwdNF98= github.com/jeremmfr/go-utils v0.9.0 h1:EMweJK12FKqRzYtISkwClVm1pnX/W7FDTqKnAm3f5X8= github.com/jeremmfr/go-utils v0.9.0/go.mod h1:Lkn95iSzCRviFhn2/0XmqzWGmxI+kkoqKAZqip7VUmM= github.com/jeremmfr/junosdecode v1.1.1 h1:wOFfJIwLXP9s0eQzzAhuX7a7N1mc+AWgDLYAmR7VoWg= @@ -136,9 +136,8 @@ github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0 github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= @@ -169,21 +168,22 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 6b1692ad596bae560c3830cb85b0442c13b06c7f Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 10 Aug 2023 09:02:05 +0200 Subject: [PATCH 08/48] tests: bump golangci-lint to v1.54 --- .github/workflows/linters.yml | 2 +- internal/providerfwk/data_source_applications.go | 2 +- internal/providerfwk/resource_forwardingoptions_sampling.go | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 7bcc7b4a..b2047bf5 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.53 + version: v1.54 args: -c .golangci.yml -v markdown-lint: diff --git a/internal/providerfwk/data_source_applications.go b/internal/providerfwk/data_source_applications.go index 32c8987b..d8fdbf4a 100644 --- a/internal/providerfwk/data_source_applications.go +++ b/internal/providerfwk/data_source_applications.go @@ -402,7 +402,7 @@ func (block *applicationsDataSourceBlockApplicationsBlockTerm) read(itemTrim str return nil } -func (dscData *applicationsDataSourceData) Filter( //nolint:gocognit +func (dscData *applicationsDataSourceData) Filter( results map[string]applicationsDataSourceBlockApplications, ) error { if v := dscData.MatchName.ValueString(); v != "" { diff --git a/internal/providerfwk/resource_forwardingoptions_sampling.go b/internal/providerfwk/resource_forwardingoptions_sampling.go index 69967491..1a510339 100644 --- a/internal/providerfwk/resource_forwardingoptions_sampling.go +++ b/internal/providerfwk/resource_forwardingoptions_sampling.go @@ -835,7 +835,6 @@ func (block *forwardingoptionsSamplingBlockInput) isEmpty() bool { } } -//nolint:gocognit func (rsc *forwardingoptionsSampling) ValidateConfig( ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, ) { From 9465aecba66f78411408017e746e9965b8cdc072 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 00:53:41 +0000 Subject: [PATCH 09/48] deps: bump github.com/jeremmfr/go-utils from 0.9.0 to 0.10.0 Bumps [github.com/jeremmfr/go-utils](https://github.com/jeremmfr/go-utils) from 0.9.0 to 0.10.0. - [Release notes](https://github.com/jeremmfr/go-utils/releases) - [Changelog](https://github.com/jeremmfr/go-utils/blob/main/CHANGELOG.md) - [Commits](https://github.com/jeremmfr/go-utils/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: github.com/jeremmfr/go-utils dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9a95919c..603c6f2c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 github.com/hashicorp/terraform-plugin-testing v1.4.0 github.com/jeremmfr/go-netconf v0.4.13 - github.com/jeremmfr/go-utils v0.9.0 + github.com/jeremmfr/go-utils v0.10.0 github.com/jeremmfr/junosdecode v1.1.1 golang.org/x/crypto v0.12.0 ) diff --git a/go.sum b/go.sum index 47466a16..618dc4d4 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jeremmfr/go-netconf v0.4.13 h1:qOTE4I4GPfsjJeeVKNNM6zgXDumA1u9bNGMiEXEPQe0= github.com/jeremmfr/go-netconf v0.4.13/go.mod h1:I0UUfijk7PhsvQqPMycuMmCx8K/z/aBZRyPzwwdNF98= -github.com/jeremmfr/go-utils v0.9.0 h1:EMweJK12FKqRzYtISkwClVm1pnX/W7FDTqKnAm3f5X8= -github.com/jeremmfr/go-utils v0.9.0/go.mod h1:Lkn95iSzCRviFhn2/0XmqzWGmxI+kkoqKAZqip7VUmM= +github.com/jeremmfr/go-utils v0.10.0 h1:gEgZzLkzzHqRS0slHv3NCEeVemBYWWF7C4M3W1EatlY= +github.com/jeremmfr/go-utils v0.10.0/go.mod h1:Lkn95iSzCRviFhn2/0XmqzWGmxI+kkoqKAZqip7VUmM= github.com/jeremmfr/junosdecode v1.1.1 h1:wOFfJIwLXP9s0eQzzAhuX7a7N1mc+AWgDLYAmR7VoWg= github.com/jeremmfr/junosdecode v1.1.1/go.mod h1:nTY0XbZC2ePbZdV0wuUboSMtGrJxtpwWVYfHjrS2Oqw= github.com/jeremmfr/terraform-plugin-sdk/v2 v2.27.1-0.20230630070723-25fea73ff21e h1:2pfGmLXTGkrY71GfmbHomxgoR5/TKDEAU0iaW+x3anc= From 2e3deeb05279878900d2827098d18ef3782b2013 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 17 Aug 2023 14:00:52 +0200 Subject: [PATCH 10/48] r/security_nat_static (& _rule): use new provider via framework --- .changes/security-nat-with-fwk.md | 9 + docs/resources/security_nat_static.md | 30 +- docs/resources/security_nat_static_rule.md | 20 +- internal/providerfwk/provider.go | 2 + .../resource_security_nat_static.go | 1026 +++++++++++++++++ .../resource_security_nat_static_rule.go | 777 +++++++++++++ .../resource_security_nat_static_rule_test.go | 14 +- .../resource_security_nat_static_test.go | 22 +- .../upgradestate_security_nat_static.go | 160 +++ .../upgradestate_security_nat_static_rule.go | 118 ++ ...radestate_security_nat_static_rule_test.go | 64 + .../upgradestate_security_nat_static_test.go | 75 ++ internal/providersdk/provider.go | 2 - .../resource_security_nat_static.go | 676 ----------- .../resource_security_nat_static_rule.go | 581 ---------- internal/tfvalidator/string_net.go | 25 + 16 files changed, 2296 insertions(+), 1305 deletions(-) create mode 100644 internal/providerfwk/resource_security_nat_static.go create mode 100644 internal/providerfwk/resource_security_nat_static_rule.go rename internal/{providersdk => providerfwk}/resource_security_nat_static_rule_test.go (94%) rename internal/{providersdk => providerfwk}/resource_security_nat_static_test.go (91%) create mode 100644 internal/providerfwk/upgradestate_security_nat_static.go create mode 100644 internal/providerfwk/upgradestate_security_nat_static_rule.go create mode 100644 internal/providerfwk/upgradestate_security_nat_static_rule_test.go create mode 100644 internal/providerfwk/upgradestate_security_nat_static_test.go delete mode 100644 internal/providersdk/resource_security_nat_static.go delete mode 100644 internal/providersdk/resource_security_nat_static_rule.go diff --git a/.changes/security-nat-with-fwk.md b/.changes/security-nat-with-fwk.md index e7871a08..7e6a73e9 100644 --- a/.changes/security-nat-with-fwk.md +++ b/.changes/security-nat-with-fwk.md @@ -13,3 +13,12 @@ ENHANCEMENTS: the resource schema has been upgraded to have one-blocks in single mode instead of list * **resource/junos_security_nat_source_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional string attributes doesn't accept *empty* value +* **resource/junos_security_nat_static**: 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 string attributes doesn't accept *empty* value + optional boolean attributes doesn't accept value *false* + the resource schema has been upgraded to have one-blocks in single mode instead of list +* **resource/junos_security_nat_static_rule**: 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 string attributes doesn't accept *empty* value + the resource schema has been upgraded to have one-blocks in single mode instead of list diff --git a/docs/resources/security_nat_static.md b/docs/resources/security_nat_static.md index 9fa99c10..e1a0c946 100644 --- a/docs/resources/security_nat_static.md +++ b/docs/resources/security_nat_static.md @@ -34,22 +34,22 @@ The following arguments are supported: -> **Note:** One of `rule` or `configure_rules_singly` arguments is required. - **name** (Required, String, Forces new resource) - The name of static nat. + Static nat rule-set name. - **from** (Required, Block) - Declare `from` configuration. + Declare where is the traffic from. - **type** (Required, String) - Type of from options. + Type of traffice source. Need to be `interface`, `routing-instance` or `zone`. - **value** (Required, Set of String) - Name of interface, routing-instance or zone for from options. + Name of interface, routing-instance or zone for traffic source. - **rule** (Optional, Block List) - For each name of rule to declare. + For each name of static nat rule to declare. See [below for nested schema](#rule-arguments). - **configure_rules_singly** (Optional, Boolean) Disable management of rules in this resource to be able to manage them with specific resources. - **description** (Optional, String) - Text description of rule set + Text description of static nat rule-set. --- @@ -58,21 +58,21 @@ The following arguments are supported: -> **Note:** One of `destination_address` or `destination_address_name` arguments is required. - **name** (Required, String) - Name of rule. + Rule name. - **destination_address** (Optional, String) - CIDR of destination address for rule match. + CIDR destination address to match. - **destination_address_name** (Optional, String) - Destination address from address book for rule match. + Destination address from address book to match. - **destination_port** (Optional, Number) - Destination port or lower limit of port range for rule match. + Destination port or lower limit of port range to match. - **destination_port_to** (Optional, Number) - Port range upper limit for rule match. + Port range upper limit to match. - **source_address** (Optional, Set of String) - List of CIDR source address for rule match. + CIDR source address to match. - **source_address_name** (Optional, Set of String) - List of source address from address book for rule match. + Source address from address book to match. - **source_port** (Optional, Set of String) - List of source port for rule match. + Source port to match. Format need to be `x` or `x to y`. - **then** (Required, Block) Declare `then` configuration. @@ -90,7 +90,7 @@ The following arguments are supported: `type` need to be `prefix` or `prefix-name`. CIDR is required if `type` = `prefix`. - **routing_instance** (Optional, String) - Change routing_instance with nat. + Name of routing instance to switch instance with nat. ## Attributes Reference diff --git a/docs/resources/security_nat_static_rule.md b/docs/resources/security_nat_static_rule.md index 32dcd957..393b3533 100644 --- a/docs/resources/security_nat_static_rule.md +++ b/docs/resources/security_nat_static_rule.md @@ -32,23 +32,23 @@ The following arguments are supported: -> **Note:** One of `destination_address` or `destination_address_name` arguments is required. - **name** (Required, String, Forces new resource) - Name of rule. + Static Rule name. - **rule_set** (Required, String, Forces new resource) - Name of rule-set. + Static nat rule-set name. - **destination_address** (Optional, String) - CIDR of destination address for rule match. + CIDR destination address to match. - **destination_address_name** (Optional, String) - Destination address from address book for rule match. + Destination address from address book to match. - **destination_port** (Optional, Number) - Destination port or lower limit of port range for rule match. + Destination port or lower limit of port range to match. - **destination_port_to** (Optional, Number) - Port range upper limit for rule match. + Port range upper limit to match. - **source_address** (Optional, Set of String) - List of CIDR source address for rule match. + CIDR source address to match. - **source_address_name** (Optional, Set of String) - List of source address from address book for rule match. + Source address from address book to match. - **source_port** (Optional, Set of String) - List of source port for rule match. + Source port to match. Format need to be `x` or `x to y`. - **then** (Required, Block) Declare `then` configuration. @@ -66,7 +66,7 @@ The following arguments are supported: `type` need to be `prefix` or `prefix-name`. CIDR is required if `type` = `prefix`. - **routing_instance** (Optional, String) - Change routing_instance with nat. + Name of routing instance to switch instance with nat. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 6a2eb123..814ff0ef 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -231,6 +231,8 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newSecurityNatDestinationPoolResource, newSecurityNatSourceResource, newSecurityNatSourcePoolResource, + newSecurityNatStaticResource, + newSecurityNatStaticRuleResource, newSecurityPolicyResource, newSecurityPolicyTunnelPairPolicyResource, newSecurityZoneResource, diff --git a/internal/providerfwk/resource_security_nat_static.go b/internal/providerfwk/resource_security_nat_static.go new file mode 100644 index 00000000..7349bf1d --- /dev/null +++ b/internal/providerfwk/resource_security_nat_static.go @@ -0,0 +1,1026 @@ +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/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 = &securityNatStatic{} + _ resource.ResourceWithConfigure = &securityNatStatic{} + _ resource.ResourceWithValidateConfig = &securityNatStatic{} + _ resource.ResourceWithImportState = &securityNatStatic{} + _ resource.ResourceWithUpgradeState = &securityNatStatic{} +) + +type securityNatStatic struct { + client *junos.Client +} + +func newSecurityNatStaticResource() resource.Resource { + return &securityNatStatic{} +} + +func (rsc *securityNatStatic) typeName() string { + return providerName + "_security_nat_static" +} + +func (rsc *securityNatStatic) junosName() string { + return "security nat static rule-set" +} + +func (rsc *securityNatStatic) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatStatic) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatStatic) 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 *securityNatStatic) 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: "Static nat rule-set name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "configure_rules_singly": schema.BoolAttribute{ + Optional: true, + Description: "Disable management of rules.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description of static nat rule-set.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.SingleNestedBlock{ + Description: "Declare where is the traffic from.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of traffice source.", + Validators: []validator.String{ + stringvalidator.OneOf("interface", "routing-instance", "zone"), + }, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "Name of interface, routing-instance or zone for traffic source.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + stringvalidator.LengthAtLeast(1), + ), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "rule": schema.ListNestedBlock{ + Description: "For each name of static nat rule to declare.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Rule name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + Description: "CIDR destination address to match.", + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + Description: "Destination address from address book to match.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + }, + }, + "destination_port": schema.Int64Attribute{ + Optional: true, + Description: "Destination port or lower limit of port range to match.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "destination_port_to": schema.Int64Attribute{ + Optional: true, + Description: "Port range upper limit to match.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "CIDR source address to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source address from address book to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + }, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source port to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d+( to \d+)?$`), + "must be use format `x` or `x to y`", + ), + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.SingleNestedBlock{ + Description: "Declare `then` action.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of static nat.", + Validators: []validator.String{ + stringvalidator.OneOf("inet", "prefix", "prefix-name"), + }, + }, + "mapped_port": schema.Int64Attribute{ + Optional: true, + Description: "Port or lower limit of port range to mapped port.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "mapped_port_to": schema.Int64Attribute{ + Optional: true, + Description: "Port range upper limit to mapped port.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "prefix": schema.StringAttribute{ + Optional: true, + Description: "CIDR or address from address book to prefix static nat.", + Validators: []validator.String{ + stringvalidator.Any( + stringvalidator.All( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Name of routing instance to switch instance with nat.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + }, + } +} + +type securityNatStaticData struct { + ConfigureRulesSingly types.Bool `tfsdk:"configure_rules_singly"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatStaticBlockFrom `tfsdk:"from"` + Rule []securityNatStaticBlockRule `tfsdk:"rule"` +} + +type securityNatStaticConfig struct { + ConfigureRulesSingly types.Bool `tfsdk:"configure_rules_singly"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From *securityNatStaticBlockFromConfig `tfsdk:"from"` + Rule types.List `tfsdk:"rule"` +} + +type securityNatStaticBlockFrom struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` +} + +type securityNatStaticBlockFromConfig struct { + Type types.String `tfsdk:"type"` + Value types.Set `tfsdk:"value"` +} + +type securityNatStaticBlockRule struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then *securityNatStaticRuleBlockThen `tfsdk:"then"` +} + +type securityNatStaticBlockRuleConfig struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress types.Set `tfsdk:"source_address"` + SourceAddressName types.Set `tfsdk:"source_address_name"` + SourcePort types.Set `tfsdk:"source_port"` + Then *securityNatStaticRuleBlockThen `tfsdk:"then"` +} + +func (rsc *securityNatStatic) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatStaticConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.ConfigureRulesSingly.IsNull() && + config.Rule.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "one of configure_rules_singly or rule must be specified", + ) + } + if !config.ConfigureRulesSingly.IsNull() && + !config.Rule.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("configure_rules_singly"), + tfdiag.MissingConfigErrSummary, + "only one of configure_rules_singly or rule must be specified", + ) + } + + if !config.Rule.IsNull() { + var rule []securityNatStaticBlockRuleConfig + asDiags := config.Rule.ElementsAs(ctx, &rule, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + ruleName := make(map[string]struct{}) + for i, block := range rule { + if !block.Name.IsUnknown() { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("name"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple rule blocks with the same name %q", name), + ) + } + ruleName[name] = struct{}{} + } + + if block.DestinationAddress.IsNull() && + block.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("destination_address"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("one of destination_address or destination_address_name must be specified"+ + " in rule block %q", block.Name.ValueString()), + ) + } + if !block.DestinationAddress.IsNull() && + !block.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("destination_address"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("destination_address and destination_address_name cannot be configured together"+ + " in rule block %q", block.Name.ValueString()), + ) + } + if !block.DestiantionPortTo.IsNull() && + block.DestinationPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("destination_port_to"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("cannot have destination_port_to without destination_port"+ + " in rule block %q", block.Name.ValueString()), + ) + } + if block.Then != nil { + if !block.Then.Type.IsUnknown() { + switch block.Then.Type.ValueString() { + case junos.InetW: + if !block.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("prefix"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } + if !block.Then.MappedPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } + if !block.Then.MappedPortTo.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port_to"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } + case "prefix": + if block.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("prefix must be specified when type = prefix"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } else if !block.Then.Prefix.IsUnknown() { + if err := tfvalidator.StringCIDRNetworkValidateAttribute(ctx, block.Then.Prefix); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("prefix"), + "Invalid CIDR Network", + err.Error(), + ) + } + } + case "prefix-name": + if block.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("prefix must be specified when type = prefix-name"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } + } + } + if !block.Then.MappedPortTo.IsNull() && + block.Then.MappedPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port_to"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("cannot have mapped_port_to without mapped_port"+ + " in then block in rule block %q", block.Name.ValueString()), + ) + } + } + } + } +} + +func (rsc *securityNatStatic) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatStaticData + 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 { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + natStaticExists, err := checkSecurityNatStaticExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if natStaticExists { + 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 { + natStaticExists, err := checkSecurityNatStaticExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !natStaticExists { + 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 *securityNatStatic) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatStaticData + 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() { + data.ConfigureRulesSingly = state.ConfigureRulesSingly + if data.ConfigureRulesSingly.ValueBool() { + data.Rule = nil + } + }, + resp, + ) +} + +func (rsc *securityNatStatic) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatStaticData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + configureRulesSingly := plan.ConfigureRulesSingly.ValueBool() + if !plan.ConfigureRulesSingly.Equal(state.ConfigureRulesSingly) { + if state.ConfigureRulesSingly.ValueBool() { + configureRulesSingly = state.ConfigureRulesSingly.ValueBool() + resp.Diagnostics.AddAttributeWarning( + path.Root("configure_rules_singly"), + "Disable configure_rules_singly on resource already created", + "It's doesn't delete rule(s) already configured. "+ + "So refresh resource after apply to detect rule(s) that need to be deleted", + ) + } else { + resp.Diagnostics.AddAttributeWarning( + path.Root("configure_rules_singly"), + "Enable configure_rules_singly on resource already created", + "It's doesn't delete rule(s) already configured. "+ + "So import rule(s) in dedicated resource(s) to be able to manage them", + ) + } + } + + if rsc.client.FakeUpdateAlso() { + junSess := rsc.client.NewSessionWithoutNetconf(ctx) + + var delErr error + if configureRulesSingly { + delErr = state.delOptsWithoutRules(ctx, junSess) + } else { + delErr = state.del(ctx, junSess) + } + if delErr != nil { + resp.Diagnostics.AddError(tfdiag.ConfigDelErrSummary, delErr.Error()) + + return + } + if errPath, err := plan.set(ctx, junSess); err != nil { + if !errPath.Equal(path.Empty()) { + resp.Diagnostics.AddAttributeError(errPath, tfdiag.ConfigSetErrSummary, err.Error()) + } else { + resp.Diagnostics.AddError(tfdiag.ConfigSetErrSummary, err.Error()) + } + + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + + return + } + + junSess, err := rsc.client.StartNewSession(ctx) + if err != nil { + resp.Diagnostics.AddError(tfdiag.StartSessErrSummary, err.Error()) + + return + } + defer junSess.Close() + if err := junSess.ConfigLock(ctx); err != nil { + resp.Diagnostics.AddError(tfdiag.ConfigLockErrSummary, err.Error()) + + return + } + defer func() { + resp.Diagnostics.Append(tfdiag.Warns(tfdiag.ConfigClearUnlockWarnSummary, junSess.ConfigClear())...) + }() + + var delErr error + if configureRulesSingly { + delErr = state.delOptsWithoutRules(ctx, junSess) + } else { + delErr = state.del(ctx, junSess) + } + if delErr != nil { + resp.Diagnostics.AddError(tfdiag.ConfigDelErrSummary, delErr.Error()) + + return + } + if errPath, err := plan.set(ctx, junSess); err != nil { + if !errPath.Equal(path.Empty()) { + resp.Diagnostics.AddAttributeError(errPath, tfdiag.ConfigSetErrSummary, err.Error()) + } else { + resp.Diagnostics.AddError(tfdiag.ConfigSetErrSummary, err.Error()) + } + + return + } + warns, err := junSess.CommitConf("update resource " + rsc.typeName()) + resp.Diagnostics.Append(tfdiag.Warns(tfdiag.ConfigCommitWarnSummary, warns)...) + if err != nil { + resp.Diagnostics.AddError(tfdiag.ConfigCommitErrSummary, err.Error()) + + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (rsc *securityNatStatic) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatStaticData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatStatic) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + junSess, err := rsc.junosClient().StartNewSession(ctx) + if err != nil { + resp.Diagnostics.AddError(tfdiag.StartSessErrSummary, err.Error()) + + return + } + defer junSess.Close() + + var data securityNatStaticData + idList := strings.Split(req.ID, junos.IDSeparator) + if err := data.read(ctx, idList[0], junSess); err != nil { + resp.Diagnostics.AddError(tfdiag.ConfigReadErrSummary, err.Error()) + + return + } + if data.ID.IsNull() { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be or "+junos.IDSeparator+"no_rules)", req.ID), + ) + } + if len(idList) > 1 && idList[1] == "no_rules" { + data.ConfigureRulesSingly = types.BoolValue(true) + data.Rule = nil + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func checkSecurityNatStaticExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat static rule-set " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatStaticData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *securityNatStaticData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatStaticData) set( + ctx context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set security nat static rule-set " + rscData.Name.ValueString() + " " + + regexpSourcePort := regexp.MustCompile(`^\d+( to \d+)?$`) + + if rscData.From != nil { + for _, value := range rscData.From.Value { + configSet = append(configSet, setPrefix+"from "+rscData.From.Type.ValueString()+" "+value.ValueString()) + } + } + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+" description \""+v+"\"") + } + + if !rscData.ConfigureRulesSingly.ValueBool() { + ruleName := make(map[string]struct{}) + for i, block := range rscData.Rule { + name := block.Name.ValueString() + if _, ok := ruleName[name]; ok { + return path.Root("rule").AtListIndex(i).AtName("name"), + fmt.Errorf("multiple rule blocks with the same name %q", name) + } + ruleName[name] = struct{}{} + + setPrefixRule := setPrefix + "rule " + name + " " + if block.DestinationAddress.IsNull() && block.DestinationAddressName.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("destination_address"), + fmt.Errorf("destination_address or destination_address_name must be specified"+ + " in rule block %q", name) + } + if !block.DestinationAddress.IsNull() && !block.DestinationAddressName.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("destination_address"), + fmt.Errorf("destination_address and destination_address_name cannot be configured together"+ + " in rule block %q", name) + } + if v := block.DestinationAddress.ValueString(); v != "" { + configSet = append(configSet, setPrefixRule+"match destination-address "+v) + } + if v := block.DestinationAddressName.ValueString(); v != "" { + configSet = append(configSet, setPrefixRule+"match destination-address-name \""+v+"\"") + } + if !block.DestinationPort.IsNull() { + configSet = append(configSet, setPrefixRule+"match destination-port "+ + utils.ConvI64toa(block.DestinationPort.ValueInt64())) + if !block.DestiantionPortTo.IsNull() { + configSet = append(configSet, setPrefixRule+"match destination-port to "+ + utils.ConvI64toa(block.DestiantionPortTo.ValueInt64())) + } + } else if !block.DestiantionPortTo.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("destination_port_to"), + fmt.Errorf("cannot have destination_port_to without destination_port"+ + " in rule block %q", name) + } + for _, v := range block.SourceAddress { + configSet = append(configSet, setPrefixRule+"match source-address "+v.ValueString()) + } + for _, v := range block.SourceAddressName { + configSet = append(configSet, setPrefixRule+"match source-address-name \""+v.ValueString()+"\"") + } + for _, v := range block.SourcePort { + if !regexpSourcePort.MatchString(v.ValueString()) { + return path.Root("rule").AtListIndex(i).AtName("source_port"), + fmt.Errorf("source_port need to have format `x` or `x to y`"+ + " in rule block %q", name) + } + configSet = append(configSet, setPrefixRule+"match source-port "+v.ValueString()) + } + if block.Then != nil { + setPrefixRuleThenStaticNat := setPrefixRule + "then static-nat " + switch thenType := block.Then.Type.ValueString(); thenType { + case junos.InetW: + if !block.Then.Prefix.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("prefix"), + fmt.Errorf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", name) + } + if !block.Then.MappedPort.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port"), + fmt.Errorf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", name) + } + if !block.Then.MappedPortTo.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port_to"), + fmt.Errorf("only routing_instance can be set when type = inet"+ + " in then block in rule block %q", name) + } + configSet = append(configSet, setPrefixRuleThenStaticNat+"inet") + if v := block.Then.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefixRuleThenStaticNat+"inet routing-instance "+v) + } + case "prefix", "prefix-name": + if block.Then.Prefix.ValueString() == "" { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("type"), + fmt.Errorf("type = %s and prefix without value"+ + " in then block in rule block %q", thenType, name) + } + switch thenType { + case "prefix": + setPrefixRuleThenStaticNat += "prefix " + if err := tfvalidator.StringCIDRNetworkValidateAttribute(ctx, block.Then.Prefix); err != nil { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("prefix"), err + } + case "prefix-name": + setPrefixRuleThenStaticNat += "prefix-name " + } + configSet = append(configSet, setPrefixRuleThenStaticNat+"\""+block.Then.Prefix.ValueString()+"\"") + + if !block.Then.MappedPort.IsNull() { + configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port "+ + utils.ConvI64toa(block.Then.MappedPort.ValueInt64())) + if !block.Then.MappedPortTo.IsNull() { + configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port to "+ + utils.ConvI64toa(block.Then.MappedPortTo.ValueInt64())) + } + } else if !block.Then.MappedPortTo.IsNull() { + return path.Root("rule").AtListIndex(i).AtName("then").AtName("mapped_port_to"), + fmt.Errorf("cannot have mapped_port_to without mapped_port"+ + " in then block in rule block %q", name) + } + if v := block.Then.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefixRuleThenStaticNat+"routing-instance "+v) + } + } + } + } + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatStaticData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat static rule-set " + 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, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "from "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { // + return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) + } + if rscData.From == nil { + rscData.From = &securityNatStaticBlockFrom{ + Type: types.StringValue(itemTrimFields[0]), + } + } + rscData.From.Value = append(rscData.From.Value, types.StringValue(itemTrimFields[1])) + case balt.CutPrefixInString(&itemTrim, "rule "): + itemTrimFields := strings.Split(itemTrim, " ") + var rule securityNatStaticBlockRule + rscData.Rule, rule = tfdata.ExtractBlockWithTFTypesString( + rscData.Rule, "Name", itemTrimFields[0], + ) + rule.Name = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + switch { + case balt.CutPrefixInString(&itemTrim, "match destination-address "): + rule.DestinationAddress = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): + rule.DestinationAddressName = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "match destination-port to "): + rule.DestiantionPortTo, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "match destination-port "): + rule.DestinationPort, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "match source-address "): + rule.SourceAddress = append(rule.SourceAddress, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "match source-address-name "): + rule.SourceAddressName = append(rule.SourceAddressName, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "match source-port "): + rule.SourcePort = append(rule.SourcePort, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "then static-nat "): + if rule.Then == nil { + rule.Then = &securityNatStaticRuleBlockThen{} + } + switch { + case balt.CutPrefixInString(&itemTrim, "prefix"): + rule.Then.Type = types.StringValue("prefix") + if balt.CutPrefixInString(&itemTrim, "-name") { + rule.Then.Type = types.StringValue("prefix-name") + } + balt.CutPrefixInString(&itemTrim, " ") + switch { + case balt.CutPrefixInString(&itemTrim, "routing-instance "): + rule.Then.RoutingInstance = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "mapped-port to "): + rule.Then.MappedPortTo, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "mapped-port "): + rule.Then.MappedPort, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + default: + rule.Then.Prefix = types.StringValue(strings.Trim(itemTrim, "\"")) + } + case balt.CutPrefixInString(&itemTrim, junos.InetW): + rule.Then.Type = types.StringValue(junos.InetW) + if balt.CutPrefixInString(&itemTrim, " routing-instance ") { + rule.Then.RoutingInstance = types.StringValue(itemTrim) + } + } + } + rscData.Rule = append(rscData.Rule, rule) + } + } + } + + return nil +} + +func (rscData *securityNatStaticData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat static rule-set " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} + +func (rscData *securityNatStaticData) delOptsWithoutRules( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat static rule-set " + rscData.Name.ValueString() + " description", + "delete security nat static rule-set " + rscData.Name.ValueString() + " from", + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providerfwk/resource_security_nat_static_rule.go b/internal/providerfwk/resource_security_nat_static_rule.go new file mode 100644 index 00000000..d42e460f --- /dev/null +++ b/internal/providerfwk/resource_security_nat_static_rule.go @@ -0,0 +1,777 @@ +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/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 = &securityNatStaticRule{} + _ resource.ResourceWithConfigure = &securityNatStaticRule{} + _ resource.ResourceWithValidateConfig = &securityNatStaticRule{} + _ resource.ResourceWithImportState = &securityNatStaticRule{} + _ resource.ResourceWithUpgradeState = &securityNatStaticRule{} +) + +type securityNatStaticRule struct { + client *junos.Client +} + +func newSecurityNatStaticRuleResource() resource.Resource { + return &securityNatStaticRule{} +} + +func (rsc *securityNatStaticRule) typeName() string { + return providerName + "_security_nat_static_rule" +} + +func (rsc *securityNatStaticRule) junosName() string { + return "security nat static rule in rule-set" +} + +func (rsc *securityNatStaticRule) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *securityNatStaticRule) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *securityNatStaticRule) 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 *securityNatStaticRule) 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: "Static Rule name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "rule_set": schema.StringAttribute{ + Required: true, + Description: "Static nat rule-set name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 31), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + Description: "CIDR destination address to match.", + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + Description: "Destination address from address book to match.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + }, + }, + "destination_port": schema.Int64Attribute{ + Optional: true, + Description: "Destination port or lower limit of port range to match.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "destination_port_to": schema.Int64Attribute{ + Optional: true, + Description: "Port range upper limit to match.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "CIDR source address to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source address from address book to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + }, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Source port to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches( + regexp.MustCompile(`^\d+( to \d+)?$`), + "must be use format `x` or `x to y`", + ), + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.SingleNestedBlock{ + Description: "Declare `then` action.", + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Description: "Type of static nat.", + Validators: []validator.String{ + stringvalidator.OneOf("inet", "prefix", "prefix-name"), + }, + }, + "mapped_port": schema.Int64Attribute{ + Optional: true, + Description: "Port or lower limit of port range to mapped port.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "mapped_port_to": schema.Int64Attribute{ + Optional: true, + Description: "Port range upper limit to mapped port.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "prefix": schema.StringAttribute{ + Optional: true, + Description: "CIDR or address from address book to prefix static nat.", + Validators: []validator.String{ + stringvalidator.Any( + stringvalidator.All( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.AddressNameFormat), + ), + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Name of routing instance to switch instance with nat.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + } +} + +type securityNatStaticRuleData struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + RuleSet types.String `tfsdk:"rule_set"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then *securityNatStaticRuleBlockThen `tfsdk:"then"` +} + +type securityNatStaticRuleConfig struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + RuleSet types.String `tfsdk:"rule_set"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress types.Set `tfsdk:"source_address"` + SourceAddressName types.Set `tfsdk:"source_address_name"` + SourcePort types.Set `tfsdk:"source_port"` + Then *securityNatStaticRuleBlockThen `tfsdk:"then"` +} + +type securityNatStaticRuleBlockThen struct { + Type types.String `tfsdk:"type"` + MappedPort types.Int64 `tfsdk:"mapped_port"` + MappedPortTo types.Int64 `tfsdk:"mapped_port_to"` + Prefix types.String `tfsdk:"prefix"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +func (rsc *securityNatStaticRule) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config securityNatStaticRuleConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.DestinationAddress.IsNull() && + config.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "one of destination_address or destination_address_name must be specified", + ) + } + if !config.DestinationAddress.IsNull() && + !config.DestinationAddressName.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("destination_address"), + tfdiag.MissingConfigErrSummary, + "only one of destination_address or destination_address_name must be specified", + ) + } + if !config.DestiantionPortTo.IsNull() && + config.DestinationPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("destination_port_to"), + tfdiag.MissingConfigErrSummary, + "cannot have destination_port_to without destination_port", + ) + } + if config.Then != nil { + if !config.Then.Type.IsUnknown() { + switch config.Then.Type.ValueString() { + case junos.InetW: + if !config.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("prefix"), + tfdiag.ConflictConfigErrSummary, + "only routing_instance can be set when type = inet"+ + " in then block", + ) + } + if !config.Then.MappedPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("mapped_port"), + tfdiag.ConflictConfigErrSummary, + "only routing_instance can be set when type = inet"+ + " in then block", + ) + } + if !config.Then.MappedPortTo.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("mapped_port_to"), + tfdiag.ConflictConfigErrSummary, + "only routing_instance can be set when type = inet"+ + " in then block", + ) + } + case "prefix": + if config.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + "prefix must be specified when type = prefix"+ + " in then block", + ) + } else if !config.Then.Prefix.IsUnknown() { + if err := tfvalidator.StringCIDRNetworkValidateAttribute(ctx, config.Then.Prefix); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("prefix"), + "Invalid CIDR Network", + err.Error(), + ) + } + } + case "prefix-name": + if config.Then.Prefix.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("type"), + tfdiag.MissingConfigErrSummary, + "prefix must be specified when type = prefix-name"+ + " in then block", + ) + } + } + } + if !config.Then.MappedPortTo.IsNull() && + config.Then.MappedPort.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("mapped_port_to"), + tfdiag.MissingConfigErrSummary, + "cannot have mapped_port_to without mapped_port"+ + " in then block", + ) + } + } +} + +func (rsc *securityNatStaticRule) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan securityNatStaticRuleData + 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.RuleSet.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("rule_set"), + "Empty rule-set", + "could not create "+rsc.junosName()+" with empty rule-set", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + if !junSess.CheckCompatibilitySecurity() { + resp.Diagnostics.AddError( + tfdiag.CompatibilityErrSummary, + fmt.Sprintf(rsc.junosName()+" not compatible "+ + "with Junos device %q", junSess.SystemInformation.HardwareModel), + ) + + return false + } + natStaticExists, err := checkSecurityNatStaticExists(fnCtx, plan.RuleSet.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !natStaticExists { + resp.Diagnostics.AddError( + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("security nat static rule-set %q does not exists", plan.RuleSet.ValueString()), + ) + + return false + } + natStaticRuleExists, err := checkSecurityNatStaticRuleExists( + fnCtx, + plan.RuleSet.ValueString(), + plan.Name.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if natStaticRuleExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf( + rsc.junosName()+" %q already exists in rule-set %q", + plan.Name.ValueString(), plan.RuleSet.ValueString(), + ), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + natStaticRuleExists, err := checkSecurityNatStaticRuleExists( + fnCtx, + plan.RuleSet.ValueString(), + plan.Name.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !natStaticRuleExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists in rule-set %q after commit "+ + "=> check your config", plan.Name.ValueString(), plan.RuleSet.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *securityNatStaticRule) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data securityNatStaticRuleData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom2String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.RuleSet.ValueString(), + state.Name.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *securityNatStaticRule) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state securityNatStaticRuleData + 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 *securityNatStaticRule) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state securityNatStaticRuleData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *securityNatStaticRule) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data securityNatStaticRuleData + + var _ resourceDataReadFrom2String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be _-_)", req.ID), + ) +} + +func checkSecurityNatStaticRuleExists( + _ context.Context, ruleSet, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat static rule-set " + ruleSet + " rule " + name + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *securityNatStaticRuleData) fillID() { + rscData.ID = types.StringValue(rscData.RuleSet.ValueString() + junos.IDSeparator + rscData.Name.ValueString()) +} + +func (rscData *securityNatStaticRuleData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *securityNatStaticRuleData) set( + ctx context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := "set security nat static " + + "rule-set " + rscData.RuleSet.ValueString() + + " rule " + rscData.Name.ValueString() + " " + configSet := []string{ + setPrefix, + } + + regexpSourcePort := regexp.MustCompile(`^\d+( to \d+)?$`) + + if v := rscData.DestinationAddress.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"match destination-address "+v) + } + if v := rscData.DestinationAddressName.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"match destination-address-name \""+v+"\"") + } + if !rscData.DestinationPort.IsNull() { + configSet = append(configSet, setPrefix+"match destination-port "+ + utils.ConvI64toa(rscData.DestinationPort.ValueInt64())) + if !rscData.DestiantionPortTo.IsNull() { + configSet = append(configSet, setPrefix+"match destination-port to "+ + utils.ConvI64toa(rscData.DestiantionPortTo.ValueInt64())) + } + } else if !rscData.DestiantionPortTo.IsNull() { + return path.Root("destination_port_to"), + fmt.Errorf("cannot have destination_port_to without destination_port") + } + for _, v := range rscData.SourceAddress { + configSet = append(configSet, setPrefix+"match source-address "+v.ValueString()) + } + for _, v := range rscData.SourceAddressName { + configSet = append(configSet, setPrefix+"match source-address-name \""+v.ValueString()+"\"") + } + for _, v := range rscData.SourcePort { + if !regexpSourcePort.MatchString(v.ValueString()) { + return path.Root("source_port"), + fmt.Errorf("source_port must be use format `x` or `x to y`") + } + configSet = append(configSet, setPrefix+"match source-port "+v.ValueString()) + } + if rscData.Then != nil { + setPrefixRuleThenStaticNat := setPrefix + "then static-nat " + switch thenType := rscData.Then.Type.ValueString(); thenType { + case junos.InetW: + if !rscData.Then.Prefix.IsNull() { + return path.Root("then").AtName("prefix"), + fmt.Errorf("only routing_instance can be set when type = inet in then block") + } + if !rscData.Then.MappedPort.IsNull() { + return path.Root("then").AtName("mapped_port"), + fmt.Errorf("only routing_instance can be set when type = inet in then block") + } + if !rscData.Then.MappedPortTo.IsNull() { + return path.Root("then").AtName("mapped_port_to"), + fmt.Errorf("only routing_instance can be set when type = inet in then block") + } + configSet = append(configSet, setPrefixRuleThenStaticNat+"inet") + if v := rscData.Then.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefixRuleThenStaticNat+"inet routing-instance "+v) + } + case "prefix", "prefix-name": + if rscData.Then.Prefix.ValueString() == "" { + return path.Root("then").AtName("type"), + fmt.Errorf("type = %s and prefix without value in then block", thenType) + } + switch thenType { + case "prefix": + setPrefixRuleThenStaticNat += "prefix " + if err := tfvalidator.StringCIDRNetworkValidateAttribute(ctx, rscData.Then.Prefix); err != nil { + return path.Root("then").AtName("prefix"), err + } + case "prefix-name": + setPrefixRuleThenStaticNat += "prefix-name " + } + configSet = append(configSet, setPrefixRuleThenStaticNat+"\""+rscData.Then.Prefix.ValueString()+"\"") + + if !rscData.Then.MappedPort.IsNull() { + configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port "+ + utils.ConvI64toa(rscData.Then.MappedPort.ValueInt64())) + if !rscData.Then.MappedPortTo.IsNull() { + configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port to "+ + utils.ConvI64toa(rscData.Then.MappedPortTo.ValueInt64())) + } + } else if !rscData.Then.MappedPortTo.IsNull() { + return path.Root("then").AtName("mapped_port_to"), + fmt.Errorf("cannot have mapped_port_to without mapped_port" + + " in then block") + } + if v := rscData.Then.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefixRuleThenStaticNat+"routing-instance "+v) + } + } + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *securityNatStaticRuleData) read( + _ context.Context, ruleSet, name string, junSess *junos.Session, +) error { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "security nat static rule-set " + ruleSet + " rule " + name + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.RuleSet = types.StringValue(ruleSet) + 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, "match destination-address "): + rscData.DestinationAddress = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): + rscData.DestinationAddressName = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "match destination-port to "): + rscData.DestiantionPortTo, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "match destination-port "): + rscData.DestinationPort, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "match source-address "): + rscData.SourceAddress = append(rscData.SourceAddress, + types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "match source-address-name "): + rscData.SourceAddressName = append(rscData.SourceAddressName, + types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "match source-port "): + rscData.SourcePort = append(rscData.SourcePort, + types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "then static-nat "): + if rscData.Then == nil { + rscData.Then = &securityNatStaticRuleBlockThen{} + } + switch { + case balt.CutPrefixInString(&itemTrim, "prefix"): + rscData.Then.Type = types.StringValue("prefix") + if balt.CutPrefixInString(&itemTrim, "-name") { + rscData.Then.Type = types.StringValue("prefix-name") + } + balt.CutPrefixInString(&itemTrim, " ") + switch { + case balt.CutPrefixInString(&itemTrim, "routing-instance "): + rscData.Then.RoutingInstance = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "mapped-port to "): + rscData.Then.MappedPortTo, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "mapped-port "): + rscData.Then.MappedPort, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + default: + rscData.Then.Prefix = types.StringValue(strings.Trim(itemTrim, "\"")) + } + case balt.CutPrefixInString(&itemTrim, junos.InetW): + rscData.Then.Type = types.StringValue(junos.InetW) + if balt.CutPrefixInString(&itemTrim, " routing-instance ") { + rscData.Then.RoutingInstance = types.StringValue(itemTrim) + } + } + } + } + } + + return nil +} + +func (rscData *securityNatStaticRuleData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete security nat static rule-set " + rscData.RuleSet.ValueString() + " rule " + rscData.Name.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_security_nat_static_rule_test.go b/internal/providerfwk/resource_security_nat_static_rule_test.go similarity index 94% rename from internal/providersdk/resource_security_nat_static_rule_test.go rename to internal/providerfwk/resource_security_nat_static_rule_test.go index 055aa2e4..2ea6bbb6 100644 --- a/internal/providersdk/resource_security_nat_static_rule_test.go +++ b/internal/providerfwk/resource_security_nat_static_rule_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 TestAccJunosSecurityNatStaticRule_basic(t *testing.T) { @@ -21,13 +21,11 @@ func TestAccJunosSecurityNatStaticRule_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", "destination_address", "192.0.2.0/25"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.#", "1"), + "then.type", "prefix"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.0.type", "prefix"), + "then.routing_instance", "testacc_securityNATSttRule"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.0.routing_instance", "testacc_securityNATSttRule"), - resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.0.prefix", "192.0.2.128/25"), + "then.prefix", "192.0.2.128/25"), ), }, { @@ -36,7 +34,7 @@ func TestAccJunosSecurityNatStaticRule_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", "destination_address", "192.0.2.0/26"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.0.prefix", "192.0.2.64/26"), + "then.prefix", "192.0.2.64/26"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule2", "destination_address_name", "testacc_securityNATSttRule2"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule2", diff --git a/internal/providersdk/resource_security_nat_static_test.go b/internal/providerfwk/resource_security_nat_static_test.go similarity index 91% rename from internal/providersdk/resource_security_nat_static_test.go rename to internal/providerfwk/resource_security_nat_static_test.go index 3c9cb7e1..a270edc4 100644 --- a/internal/providersdk/resource_security_nat_static_test.go +++ b/internal/providerfwk/resource_security_nat_static_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 TestAccJunosSecurityNatStatic_basic(t *testing.T) { @@ -17,13 +17,11 @@ func TestAccJunosSecurityNatStatic_basic(t *testing.T) { Config: testAccJunosSecurityNatStaticConfigCreate(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "from.#", "1"), + "from.type", "zone"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "from.0.type", "zone"), + "from.value.#", "1"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "from.0.value.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "from.0.value.0", "testacc_securityNATStt"), + "from.value.0", "testacc_securityNATStt"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", "rule.#", "2"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", @@ -31,13 +29,11 @@ func TestAccJunosSecurityNatStatic_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", "rule.0.destination_address", "192.0.2.0/25"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "rule.0.then.#", "1"), - resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "rule.0.then.0.type", "prefix"), + "rule.0.then.type", "prefix"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "rule.0.then.0.routing_instance", "testacc_securityNATStt"), + "rule.0.then.routing_instance", "testacc_securityNATStt"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "rule.0.then.0.prefix", "192.0.2.128/25"), + "rule.0.then.prefix", "192.0.2.128/25"), ), }, { @@ -48,7 +44,7 @@ func TestAccJunosSecurityNatStatic_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", "rule.0.destination_address", "192.0.2.0/26"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", - "rule.0.then.0.prefix", "192.0.2.64/26"), + "rule.0.then.prefix", "192.0.2.64/26"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", "rule.1.destination_address_name", "testacc_securityNATSttRule2"), resource.TestCheckResourceAttr("junos_security_nat_static.testacc_securityNATStt", diff --git a/internal/providerfwk/upgradestate_security_nat_static.go b/internal/providerfwk/upgradestate_security_nat_static.go new file mode 100644 index 00000000..a9b6d9e2 --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_static.go @@ -0,0 +1,160 @@ +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 *securityNatStatic) 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, + }, + "configure_rules_singly": schema.BoolAttribute{ + Optional: true, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "value": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + "rule": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + }, + "destination_port": schema.Int64Attribute{ + Optional: true, + }, + "destination_port_to": schema.Int64Attribute{ + Optional: true, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "mapped_port": schema.Int64Attribute{ + Optional: true, + }, + "mapped_port_to": schema.Int64Attribute{ + Optional: true, + }, + "prefix": schema.StringAttribute{ + Optional: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeSecurityNatStaticV0toV1, + }, + } +} + +func upgradeSecurityNatStaticV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + ConfigureRulesSingly types.Bool `tfsdk:"configure_rules_singly"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []securityNatStaticBlockFrom `tfsdk:"from"` + Rule []struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then []securityNatStaticRuleBlockThen `tfsdk:"then"` + } `tfsdk:"rule"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 securityNatStaticData + dataV1.ID = dataV0.ID + dataV1.Name = dataV0.Name + dataV1.ConfigureRulesSingly = dataV0.ConfigureRulesSingly + dataV1.Description = dataV0.Description + if len(dataV0.From) > 0 { + dataV1.From = &dataV0.From[0] + } + for _, blockV0 := range dataV0.Rule { + blockV1 := securityNatStaticBlockRule{ + Name: blockV0.Name, + DestinationAddress: blockV0.DestinationAddress, + DestinationAddressName: blockV0.DestinationAddressName, + DestinationPort: blockV0.DestinationPort, + DestiantionPortTo: blockV0.DestiantionPortTo, + SourceAddress: blockV0.SourceAddress, + SourceAddressName: blockV0.SourceAddressName, + SourcePort: blockV0.SourcePort, + } + if len(blockV0.Then) > 0 { + blockV1.Then = &blockV0.Then[0] + } + dataV1.Rule = append(dataV1.Rule, blockV1) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_security_nat_static_rule.go b/internal/providerfwk/upgradestate_security_nat_static_rule.go new file mode 100644 index 00000000..6d0bb82e --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_static_rule.go @@ -0,0 +1,118 @@ +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 *securityNatStaticRule) 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, + }, + "rule_set": schema.StringAttribute{ + Required: true, + }, + "destination_address": schema.StringAttribute{ + Optional: true, + }, + "destination_address_name": schema.StringAttribute{ + Optional: true, + }, + "destination_port": schema.Int64Attribute{ + Optional: true, + }, + "destination_port_to": schema.Int64Attribute{ + Optional: true, + }, + "source_address": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_address_name": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "source_port": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "then": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "mapped_port": schema.Int64Attribute{ + Optional: true, + }, + "mapped_port_to": schema.Int64Attribute{ + Optional: true, + }, + "prefix": schema.StringAttribute{ + Optional: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeSecurityNatStaticRuleV0toV1, + }, + } +} + +func upgradeSecurityNatStaticRuleV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + RuleSet types.String `tfsdk:"rule_set"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then []securityNatStaticRuleBlockThen `tfsdk:"then"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 securityNatStaticRuleData + dataV1.ID = dataV0.ID + dataV1.Name = dataV0.Name + dataV1.RuleSet = dataV0.RuleSet + dataV1.DestinationAddress = dataV0.DestinationAddress + dataV1.DestinationAddressName = dataV0.DestinationAddressName + dataV1.DestinationPort = dataV0.DestinationPort + dataV1.DestiantionPortTo = dataV0.DestiantionPortTo + dataV1.SourceAddress = dataV0.SourceAddress + dataV1.SourceAddressName = dataV0.SourceAddressName + dataV1.SourcePort = dataV0.SourcePort + if len(dataV0.Then) > 0 { + dataV1.Then = &dataV0.Then[0] + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_security_nat_static_rule_test.go b/internal/providerfwk/upgradestate_security_nat_static_rule_test.go new file mode 100644 index 00000000..adb8bbc3 --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_static_rule_test.go @@ -0,0 +1,64 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosSecurityNatStaticRuleUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_UPGRADE_STATE") == "" { + return + } + if os.Getenv("TESTACC_SRX") != "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosSecurityNatStaticRuleConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosSecurityNatStaticRuleConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosSecurityNatStaticRuleConfigV0() string { + return ` +resource "junos_security_nat_static" "testacc_securityNATSttRule" { + name = "testacc_secNATSttRule_upgrade" + from { + type = "zone" + value = [junos_security_zone.testacc_securityNATSttRule.name] + } + configure_rules_singly = true +} +resource "junos_security_nat_static_rule" "testacc_securityNATSttRule" { + name = "testacc_secNATSttRule_upgrade" + rule_set = junos_security_nat_static.testacc_securityNATSttRule.name + destination_address = "192.0.2.0/25" + then { + type = "prefix" + prefix = "192.0.2.128/25" + } +} +resource "junos_security_zone" "testacc_securityNATSttRule" { + name = "testacc_securityNATSttRule_upgrade" +} +` +} diff --git a/internal/providerfwk/upgradestate_security_nat_static_test.go b/internal/providerfwk/upgradestate_security_nat_static_test.go new file mode 100644 index 00000000..30916327 --- /dev/null +++ b/internal/providerfwk/upgradestate_security_nat_static_test.go @@ -0,0 +1,75 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosSecurityNatStaticUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_UPGRADE_STATE") == "" { + return + } + if os.Getenv("TESTACC_SRX") != "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosSecurityNatStaticConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosSecurityNatStaticConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosSecurityNatStaticConfigV0() string { + return ` +resource "junos_security_nat_static" "testacc_securityNATStt" { + name = "testacc_secNATStt_upgrade" + description = "testacc securityNATStt upgrade" + from { + type = "zone" + value = [junos_security_zone.testacc_securityNATStt.name] + } + rule { + name = "testacc_securityNATSttRule" + destination_address = "192.0.2.0/25" + then { + type = "prefix" + routing_instance = junos_routing_instance.testacc_securityNATStt.name + prefix = "192.0.2.128/25" + } + } + rule { + name = "testacc_securityNATSttRule2" + destination_address = "64:ff9b::/96" + then { + type = "inet" + } + } +} + +resource "junos_security_zone" "testacc_securityNATStt" { + name = "testacc_securityNATStt_upgrade" +} +resource "junos_routing_instance" "testacc_securityNATStt" { + name = "testacc_securityNATStt_upgrade" +} +` +} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 25b82dd7..f3034419 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -178,8 +178,6 @@ func Provider() *schema.Provider { "junos_security_idp_custom_attack_group": resourceSecurityIdpCustomAttackGroup(), "junos_security_idp_policy": resourceSecurityIdpPolicy(), "junos_security_log_stream": resourceSecurityLogStream(), - "junos_security_nat_static": resourceSecurityNatStatic(), - "junos_security_nat_static_rule": resourceSecurityNatStaticRule(), "junos_security_screen": resourceSecurityScreen(), "junos_security_screen_whitelist": resourceSecurityScreenWhiteList(), "junos_security_utm_custom_url_category": resourceSecurityUtmCustomURLCategory(), diff --git a/internal/providersdk/resource_security_nat_static.go b/internal/providersdk/resource_security_nat_static.go deleted file mode 100644 index ca09c941..00000000 --- a/internal/providersdk/resource_security_nat_static.go +++ /dev/null @@ -1,676 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/go-cty/cty" - "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" - bchk "github.com/jeremmfr/go-utils/basiccheck" -) - -type natStaticOptions struct { - name string - description string - from []map[string]interface{} - rule []map[string]interface{} -} - -func resourceSecurityNatStatic() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatStaticCreate, - ReadWithoutTimeout: resourceSecurityNatStaticRead, - UpdateWithoutTimeout: resourceSecurityNatStaticUpdate, - DeleteWithoutTimeout: resourceSecurityNatStaticDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatStaticImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "from": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"interface", "routing-instance", "zone"}, false), - }, - "value": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "configure_rules_singly": { - Type: schema.TypeBool, - Optional: true, - ExactlyOneOf: []string{"configure_rules_singly", "rule"}, - }, - "rule": { - Type: schema.TypeList, - Optional: true, - ExactlyOneOf: []string{"rule", "configure_rules_singly"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "destination_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "destination_address_name": { - Type: schema.TypeString, - Optional: true, - }, - "destination_port": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "destination_port_to": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "source_address": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - "source_address_name": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_port": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "then": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"inet", "prefix", "prefix-name"}, false), - }, - "mapped_port": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "mapped_port_to": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "prefix": { - Type: schema.TypeString, - Optional: true, - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - }, - }, - }, - }, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourceSecurityNatStaticCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatStatic(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 !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat static not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - securityNatStaticExists, err := checkSecurityNatStaticExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatStaticExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat static %v already exists", d.Get("name").(string)))...) - } - - if err := setSecurityNatStatic(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_static") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - securityNatStaticExists, err = checkSecurityNatStaticExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if securityNatStaticExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("security nat static %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourceSecurityNatStaticReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatStaticRead(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 resourceSecurityNatStaticReadWJunSess(d, junSess) -} - -func resourceSecurityNatStaticReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natStaticOptions, err := readSecurityNatStatic(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natStaticOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatStaticData(d, natStaticOptions) - } - - return nil -} - -func resourceSecurityNatStaticUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - d.Partial(true) - var diagWarns diag.Diagnostics - configureRulesSingly := d.Get("configure_rules_singly").(bool) - if d.HasChange("configure_rules_singly") { - if o, _ := d.GetChange("configure_rules_singly"); o.(bool) { - configureRulesSingly = o.(bool) - diagWarns = append(diagWarns, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Disable configure_rules_singly on resource already created doesn't " + - "delete rule(s) already configured.", - Detail: "So refresh resource after apply to detect rule(s) that need to be deleted", - AttributePath: cty.Path{cty.GetAttrStep{Name: "configure_rules_singly"}}, - }) - } else { - diagWarns = append(diagWarns, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Enable configure_rules_singly on resource already created doesn't " + - "delete rules already configured.", - Detail: "So import rule(s) in dedicated resource(s) to be able to manage them", - AttributePath: cty.Path{cty.GetAttrStep{Name: "configure_rules_singly"}}, - }) - } - } - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if configureRulesSingly { - if err := delSecurityNatStaticWithoutRules(d.Get("name").(string), junSess); err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - } else { - if err := delSecurityNatStatic(d.Get("name").(string), junSess); err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - } - if err := setSecurityNatStatic(d, junSess); err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return diagWarns - } - 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) - } - if configureRulesSingly { - if err := delSecurityNatStaticWithoutRules(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - } else { - if err := delSecurityNatStatic(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - } - if err := setSecurityNatStatic(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_static") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatStaticReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatStaticDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatStatic(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 := delSecurityNatStatic(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_static") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatStaticImport(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) - idList := strings.Split(d.Id(), junos.IDSeparator) - securityNatStaticExists, err := checkSecurityNatStaticExists(idList[0], junSess) - if err != nil { - return nil, err - } - if !securityNatStaticExists { - return nil, fmt.Errorf("don't find nat static with id '%v' "+ - "(id must be or "+junos.IDSeparator+"no_rules)", idList[0]) - } - natStaticOptions, err := readSecurityNatStatic(idList[0], junSess) - if err != nil { - return nil, err - } - if len(idList) > 1 && idList[1] == "no_rules" { - if tfErr := d.Set("configure_rules_singly", true); tfErr != nil { - panic(tfErr) - } - } - fillSecurityNatStaticData(d, natStaticOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatStaticExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat static rule-set " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatStatic(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - regexpSourcePort := regexp.MustCompile(`^\d+( to \d+)?$`) - - setPrefix := "set security nat static rule-set " + d.Get("name").(string) - for _, v := range d.Get("from").([]interface{}) { - from := v.(map[string]interface{}) - for _, value := range sortSetOfString(from["value"].(*schema.Set).List()) { - configSet = append(configSet, setPrefix+" from "+from["type"].(string)+" "+value) - } - } - ruleNameList := make([]string, 0) - if !d.Get("configure_rules_singly").(bool) { - for _, v := range d.Get("rule").([]interface{}) { - rule := v.(map[string]interface{}) - if bchk.InSlice(rule["name"].(string), ruleNameList) { - return fmt.Errorf("multiple blocks rule with the same name %s", rule["name"].(string)) - } - ruleNameList = append(ruleNameList, rule["name"].(string)) - setPrefixRule := setPrefix + " rule " + rule["name"].(string) - if rule["destination_address"].(string) == "" && rule["destination_address_name"].(string) == "" { - return fmt.Errorf("missing destination_address or destination_address_name in rule %s", rule["name"].(string)) - } - if rule["destination_address"].(string) != "" && rule["destination_address_name"].(string) != "" { - return fmt.Errorf("destination_address and destination_address_name must not be set at the same time "+ - "in rule %s", rule["name"].(string)) - } - if vv := rule["destination_address"].(string); vv != "" { - configSet = append(configSet, setPrefixRule+" match destination-address "+vv) - } - if vv := rule["destination_address_name"].(string); vv != "" { - configSet = append(configSet, setPrefixRule+" match destination-address-name \""+vv+"\"") - } - if vv := rule["destination_port"].(int); vv != 0 { - configSet = append(configSet, setPrefixRule+" match destination-port "+strconv.Itoa(vv)) - if vvTo := rule["destination_port_to"].(int); vvTo != 0 { - configSet = append(configSet, setPrefixRule+" match destination-port to "+strconv.Itoa(vvTo)) - } - } else if rule["destination_port_to"].(int) != 0 { - return fmt.Errorf("destination_port need to be set with destination_port_to in rule %s", rule["name"].(string)) - } - for _, vv := range sortSetOfString(rule["source_address"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address "+vv) - } - for _, vv := range sortSetOfString(rule["source_address_name"].(*schema.Set).List()) { - configSet = append(configSet, setPrefixRule+" match source-address-name \""+vv+"\"") - } - for _, vv := range sortSetOfString(rule["source_port"].(*schema.Set).List()) { - if !regexpSourcePort.MatchString(vv) { - return fmt.Errorf("source_port need to have format `x` or `x to y` in rule %s", rule["name"].(string)) - } - configSet = append(configSet, setPrefixRule+" match source-port "+vv) - } - for _, thenV := range rule["then"].([]interface{}) { - then := thenV.(map[string]interface{}) - if then["type"].(string) == junos.InetW { - if then["prefix"].(string) != "" || - then["mapped_port"].(int) != 0 || - then["mapped_port_to"].(int) != 0 { - return fmt.Errorf("only routing_instance can be set in rule %s with type = inet", rule["name"].(string)) - } - configSet = append(configSet, setPrefixRule+" then static-nat inet") - if rI := then["routing_instance"].(string); rI != "" { - configSet = append(configSet, setPrefixRule+" then static-nat inet routing-instance "+rI) - } - } - if then["type"].(string) == "prefix" || then["type"].(string) == "prefix-name" { - setPrefixRuleThenStaticNat := setPrefixRule + " then static-nat " - if then["type"].(string) == "prefix" { - setPrefixRuleThenStaticNat += "prefix " - if then["prefix"].(string) == "" { - return fmt.Errorf("missing prefix in rule %s with type = prefix", rule["name"].(string)) - } - if err := validateCIDRNetwork(then["prefix"].(string)); err != nil { - return err - } - } - if then["type"].(string) == "prefix-name" { - setPrefixRuleThenStaticNat += "prefix-name " - if then["prefix"].(string) == "" { - return fmt.Errorf("missing prefix in rule %s with type = prefix-name", rule["name"].(string)) - } - } - configSet = append(configSet, setPrefixRuleThenStaticNat+"\""+then["prefix"].(string)+"\"") - if vv := then["mapped_port"].(int); vv != 0 { - configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port "+strconv.Itoa(vv)) - if vvTo := then["mapped_port_to"].(int); vvTo != 0 { - configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port to "+strconv.Itoa(vvTo)) - } - } else if then["mapped_port_to"].(int) != 0 { - return fmt.Errorf("mapped_port need to set with mapped_port_to in rule %s", rule["name"].(string)) - } - if vv := then["routing_instance"].(string); vv != "" { - configSet = append(configSet, setPrefixRuleThenStaticNat+"routing-instance "+vv) - } - } - } - } - } - if v := d.Get("description").(string); v != "" { - configSet = append(configSet, setPrefix+" description \""+v+"\"") - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatStatic(name string, junSess *junos.Session, -) (confRead natStaticOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat static rule-set " + 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, "from "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { // - return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "from", itemTrim) - } - if len(confRead.from) == 0 { - confRead.from = append(confRead.from, map[string]interface{}{ - "type": itemTrimFields[0], - "value": make([]string, 0), - }) - } - confRead.from[0]["value"] = append(confRead.from[0]["value"].([]string), itemTrimFields[1]) - case balt.CutPrefixInString(&itemTrim, "rule "): - itemTrimFields := strings.Split(itemTrim, " ") - ruleOptions := map[string]interface{}{ - "name": itemTrimFields[0], - "destination_address": "", - "destination_address_name": "", - "destination_port": 0, - "destination_port_to": 0, - "source_address": make([]string, 0), - "source_address_name": make([]string, 0), - "source_port": make([]string, 0), - "then": make([]map[string]interface{}, 0), - } - confRead.rule = copyAndRemoveItemMapList("name", ruleOptions, confRead.rule) - balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") - switch { - case balt.CutPrefixInString(&itemTrim, "match destination-address "): - ruleOptions["destination_address"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): - ruleOptions["destination_address_name"] = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "match destination-port to "): - ruleOptions["destination_port_to"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "match destination-port "): - ruleOptions["destination_port"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "match source-address "): - ruleOptions["source_address"] = append( - ruleOptions["source_address"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "match source-address-name "): - ruleOptions["source_address_name"] = append( - ruleOptions["source_address_name"].([]string), - strings.Trim(itemTrim, "\""), - ) - case balt.CutPrefixInString(&itemTrim, "match source-port "): - ruleOptions["source_port"] = append( - ruleOptions["source_port"].([]string), - itemTrim, - ) - case balt.CutPrefixInString(&itemTrim, "then static-nat "): - if len(ruleOptions["then"].([]map[string]interface{})) == 0 { - ruleOptions["then"] = append(ruleOptions["then"].([]map[string]interface{}), - map[string]interface{}{ - "type": "", - "mapped_port": 0, - "mapped_port_to": 0, - "prefix": "", - "routing_instance": "", - }) - } - ruleThenOptions := ruleOptions["then"].([]map[string]interface{})[0] - switch { - case balt.CutPrefixInString(&itemTrim, "prefix"): - ruleThenOptions["type"] = "prefix" - if balt.CutPrefixInString(&itemTrim, "-name") { - ruleThenOptions["type"] = "prefix-name" - } - balt.CutPrefixInString(&itemTrim, " ") - switch { - case balt.CutPrefixInString(&itemTrim, "routing-instance "): - ruleThenOptions["routing_instance"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "mapped-port to "): - ruleThenOptions["mapped_port_to"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "mapped-port "): - ruleThenOptions["mapped_port"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - default: - ruleThenOptions["prefix"] = strings.Trim(itemTrim, "\"") - } - case balt.CutPrefixInString(&itemTrim, junos.InetW): - ruleThenOptions["type"] = junos.InetW - if balt.CutPrefixInString(&itemTrim, " routing-instance ") { - ruleThenOptions["routing_instance"] = itemTrim - } - } - } - confRead.rule = append(confRead.rule, ruleOptions) - case balt.CutPrefixInString(&itemTrim, "description "): - confRead.description = strings.Trim(itemTrim, "\"") - } - } - } - - return confRead, nil -} - -func delSecurityNatStatic(natStatic string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat static rule-set "+natStatic) - - return junSess.ConfigSet(configSet) -} - -func delSecurityNatStaticWithoutRules(natStatic string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete security nat static rule-set "+natStatic+" description") - configSet = append(configSet, "delete security nat static rule-set "+natStatic+" from") - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatStaticData(d *schema.ResourceData, natStaticOptions natStaticOptions) { - if tfErr := d.Set("name", natStaticOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("from", natStaticOptions.from); tfErr != nil { - panic(tfErr) - } - if !d.Get("configure_rules_singly").(bool) { - if tfErr := d.Set("rule", natStaticOptions.rule); tfErr != nil { - panic(tfErr) - } - } - if tfErr := d.Set("description", natStaticOptions.description); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/providersdk/resource_security_nat_static_rule.go b/internal/providersdk/resource_security_nat_static_rule.go deleted file mode 100644 index 3f63bccf..00000000 --- a/internal/providersdk/resource_security_nat_static_rule.go +++ /dev/null @@ -1,581 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "regexp" - "strconv" - "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" -) - -type natStaticRuleOptions struct { - destinationPort int - destinationPortTo int - name string - destinationAddress string - destinationAddressName string - ruleSet string - sourceAddress []string - sourceAddressName []string - sourcePort []string - then []map[string]interface{} -} - -func resourceSecurityNatStaticRule() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceSecurityNatStaticRuleCreate, - ReadWithoutTimeout: resourceSecurityNatStaticRuleRead, - UpdateWithoutTimeout: resourceSecurityNatStaticRuleUpdate, - DeleteWithoutTimeout: resourceSecurityNatStaticRuleDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceSecurityNatStaticRuleImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "rule_set": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 32, formatDefault), - }, - "destination_address": { - Type: schema.TypeString, - Optional: true, - ExactlyOneOf: []string{"destination_address", "destination_address_name"}, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "destination_address_name": { - Type: schema.TypeString, - Optional: true, - ExactlyOneOf: []string{"destination_address", "destination_address_name"}, - }, - "destination_port": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "destination_port_to": { - Type: schema.TypeInt, - Optional: true, - RequiredWith: []string{"destination_port"}, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "source_address": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - "source_address_name": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "source_port": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "then": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"inet", "prefix", "prefix-name"}, false), - }, - "mapped_port": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "mapped_port_to": { - Type: schema.TypeInt, - Optional: true, - RequiredWith: []string{"then.0.mapped_port"}, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "prefix": { - Type: schema.TypeString, - Optional: true, - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - }, - }, - }, - } -} - -func resourceSecurityNatStaticRuleCreate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setSecurityNatStaticRule(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("rule_set").(string) + junos.IDSeparator + d.Get("name").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if !junSess.CheckCompatibilitySecurity() { - return diag.FromErr(fmt.Errorf("security nat static rule in rule-set not compatible with Junos device %s", - junSess.SystemInformation.HardwareModel)) - } - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - natStaticExists, err := checkSecurityNatStaticExists(d.Get("rule_set").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if !natStaticExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("security nat static rule-set %v doesn't exist", d.Get("rule_set").(string)))...) - } - natStaticRuleExists, err := checkSecurityNatStaticRuleExists( - d.Get("rule_set").(string), - d.Get("name").(string), - junSess, - ) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if natStaticRuleExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf( - "security nat static rule %v already exists in rule-set %s", - d.Get("name").(string), d.Get("rule_set").(string)))...) - } - - if err := setSecurityNatStaticRule(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_security_nat_static_rule") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - natStaticRuleExists, err = checkSecurityNatStaticRuleExists( - d.Get("rule_set").(string), - d.Get("name").(string), - junSess, - ) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if natStaticRuleExists { - d.SetId(d.Get("rule_set").(string) + junos.IDSeparator + d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf( - "security nat statuc rule %v not exists in rule-set %s after commit "+ - "=> check your config", d.Get("name").(string), d.Get("rule_set").(string)))...) - } - - return append(diagWarns, resourceSecurityNatStaticRuleReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatStaticRuleRead(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 resourceSecurityNatStaticRuleReadWJunSess(d, junSess) -} - -func resourceSecurityNatStaticRuleReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - natStaticRuleOptions, err := readSecurityNatStaticRule( - d.Get("rule_set").(string), - d.Get("name").(string), - junSess, - ) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if natStaticRuleOptions.name == "" { - d.SetId("") - } else { - fillSecurityNatStaticRuleData(d, natStaticRuleOptions) - } - - return nil -} - -func resourceSecurityNatStaticRuleUpdate(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 := delSecurityNatStaticRule(d.Get("rule_set").(string), d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setSecurityNatStaticRule(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 := delSecurityNatStaticRule(d.Get("rule_set").(string), d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setSecurityNatStaticRule(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_security_nat_static_rule") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceSecurityNatStaticRuleReadWJunSess(d, junSess)...) -} - -func resourceSecurityNatStaticRuleDelete(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delSecurityNatStaticRule(d.Get("rule_set").(string), 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 := delSecurityNatStaticRule(d.Get("rule_set").(string), d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_security_nat_static_rule") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceSecurityNatStaticRuleImport(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) - idList := strings.Split(d.Id(), junos.IDSeparator) - if len(idList) < 2 { - return nil, fmt.Errorf("missing element(s) in id with separator %v", junos.IDSeparator) - } - natStaticRuleExists, err := checkSecurityNatStaticRuleExists(idList[0], idList[1], junSess) - if err != nil { - return nil, err - } - if !natStaticRuleExists { - return nil, fmt.Errorf( - "don't find static nat rule with id '%v' (id must be "+junos.IDSeparator+")", d.Id()) - } - natStaticRuleOptions, err := readSecurityNatStaticRule(idList[0], idList[1], junSess) - if err != nil { - return nil, err - } - fillSecurityNatStaticRuleData(d, natStaticRuleOptions) - - result[0] = d - - return result, nil -} - -func checkSecurityNatStaticRuleExists(ruleSet, name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat static rule-set " + ruleSet + " rule " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setSecurityNatStaticRule(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - regexpSourcePort := regexp.MustCompile(`^\d+( to \d+)?$`) - - setPrefix := "set security nat static rule-set " + d.Get("rule_set").(string) + - " rule " + d.Get("name").(string) + " " - configSet = append(configSet, setPrefix) - if v := d.Get("destination_address").(string); v != "" { - configSet = append(configSet, setPrefix+"match destination-address "+v) - } - if v := d.Get("destination_address_name").(string); v != "" { - configSet = append(configSet, setPrefix+"match destination-address-name \""+v+"\"") - } - if v := d.Get("destination_port").(int); v != 0 { - configSet = append(configSet, setPrefix+"match destination-port "+strconv.Itoa(v)) - if vv := d.Get("destination_port_to").(int); vv != 0 { - configSet = append(configSet, setPrefix+"match destination-port to "+strconv.Itoa(vv)) - } - } else if d.Get("destination_port_to").(int) != 0 { - return fmt.Errorf("destination_port need to be not 0 with destination_port_to") - } - for _, v := range sortSetOfString(d.Get("source_address").(*schema.Set).List()) { - configSet = append(configSet, setPrefix+"match source-address "+v) - } - for _, v := range sortSetOfString(d.Get("source_address_name").(*schema.Set).List()) { - configSet = append(configSet, setPrefix+"match source-address-name \""+v+"\"") - } - for _, v := range sortSetOfString(d.Get("source_port").(*schema.Set).List()) { - if !regexpSourcePort.MatchString(v) { - return fmt.Errorf("source_port need to have format `x` or `x to y`") - } - configSet = append(configSet, setPrefix+"match source-port "+v) - } - for _, v := range d.Get("then").([]interface{}) { - then := v.(map[string]interface{}) - if then["type"].(string) == junos.InetW { - if then["prefix"].(string) != "" || - then["mapped_port"].(int) != 0 || - then["mapped_port_to"].(int) != 0 { - return fmt.Errorf("only routing_instance can be set with type = inet") - } - configSet = append(configSet, setPrefix+"then static-nat inet") - if rI := then["routing_instance"].(string); rI != "" { - configSet = append(configSet, setPrefix+"then static-nat inet routing-instance "+rI) - } - } - if then["type"].(string) == "prefix" || then["type"].(string) == "prefix-name" { - setPrefixRuleThenStaticNat := setPrefix + "then static-nat " - if then["type"].(string) == "prefix" { - setPrefixRuleThenStaticNat += "prefix " - if then["prefix"].(string) == "" { - return fmt.Errorf("missing prefix with type = prefix") - } - if err := validateCIDRNetwork(then["prefix"].(string)); err != nil { - return err - } - } - if then["type"].(string) == "prefix-name" { - setPrefixRuleThenStaticNat += "prefix-name " - if then["prefix"].(string) == "" { - return fmt.Errorf("missing prefix with type = prefix-name") - } - } - configSet = append(configSet, setPrefixRuleThenStaticNat+"\""+then["prefix"].(string)+"\"") - if vv := then["mapped_port"].(int); vv != 0 { - configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port "+strconv.Itoa(vv)) - if vvTo := then["mapped_port_to"].(int); vvTo != 0 { - configSet = append(configSet, setPrefixRuleThenStaticNat+"mapped-port to "+strconv.Itoa(vvTo)) - } - } else if then["mapped_port_to"].(int) != 0 { - return fmt.Errorf("mapped_port need to be not 0 with mapped_port_to") - } - if vv := then["routing_instance"].(string); vv != "" { - configSet = append(configSet, setPrefixRuleThenStaticNat+"routing-instance "+vv) - } - } - } - - return junSess.ConfigSet(configSet) -} - -func readSecurityNatStaticRule(ruleSet, name string, junSess *junos.Session, -) (confRead natStaticRuleOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "security nat static rule-set " + ruleSet + " rule " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - confRead.ruleSet = ruleSet - 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, "match destination-address "): - confRead.destinationAddress = itemTrim - case balt.CutPrefixInString(&itemTrim, "match destination-address-name "): - confRead.destinationAddressName = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "match destination-port to "): - confRead.destinationPortTo, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "match destination-port "): - confRead.destinationPort, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "match source-address "): - confRead.sourceAddress = append(confRead.sourceAddress, itemTrim) - case balt.CutPrefixInString(&itemTrim, "match source-address-name "): - confRead.sourceAddressName = append(confRead.sourceAddressName, strings.Trim(itemTrim, "\"")) - case balt.CutPrefixInString(&itemTrim, "match source-port "): - confRead.sourcePort = append(confRead.sourcePort, itemTrim) - case balt.CutPrefixInString(&itemTrim, "then static-nat "): - if len(confRead.then) == 0 { - confRead.then = append(confRead.then, map[string]interface{}{ - "type": "", - "mapped_port": 0, - "mapped_port_to": 0, - "prefix": "", - "routing_instance": "", - }) - } - ruleThenOptions := confRead.then[0] - switch { - case balt.CutPrefixInString(&itemTrim, "prefix"): - ruleThenOptions["type"] = "prefix" - if balt.CutPrefixInString(&itemTrim, "-name") { - ruleThenOptions["type"] = "prefix-name" - } - balt.CutPrefixInString(&itemTrim, " ") - switch { - case balt.CutPrefixInString(&itemTrim, "routing-instance "): - ruleThenOptions["routing_instance"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "mapped-port to "): - ruleThenOptions["mapped_port_to"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "mapped-port "): - ruleThenOptions["mapped_port"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - default: - ruleThenOptions["prefix"] = strings.Trim(itemTrim, "\"") - } - case balt.CutPrefixInString(&itemTrim, junos.InetW): - ruleThenOptions["type"] = junos.InetW - if balt.CutPrefixInString(&itemTrim, " routing-instance ") { - ruleThenOptions["routing_instance"] = itemTrim - } - } - } - } - } - - return confRead, nil -} - -func delSecurityNatStaticRule(ruleSet, name string, junSess *junos.Session) error { - configSet := []string{"delete security nat static rule-set " + ruleSet + " rule " + name} - - return junSess.ConfigSet(configSet) -} - -func fillSecurityNatStaticRuleData(d *schema.ResourceData, natStaticRuleOptions natStaticRuleOptions) { - if tfErr := d.Set("name", natStaticRuleOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("rule_set", natStaticRuleOptions.ruleSet); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("destination_address", natStaticRuleOptions.destinationAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("destination_address_name", natStaticRuleOptions.destinationAddressName); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("destination_port", natStaticRuleOptions.destinationPort); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("destination_port_to", natStaticRuleOptions.destinationPortTo); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("source_address", natStaticRuleOptions.sourceAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("source_address_name", natStaticRuleOptions.sourceAddressName); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("source_port", natStaticRuleOptions.sourcePort); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("then", natStaticRuleOptions.then); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/tfvalidator/string_net.go b/internal/tfvalidator/string_net.go index 25aa6418..1c2a7839 100644 --- a/internal/tfvalidator/string_net.go +++ b/internal/tfvalidator/string_net.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" bchk "github.com/jeremmfr/go-utils/basiccheck" ) @@ -216,6 +217,30 @@ func (v StringCIDRNetworkValidator) ValidateString( } } +func StringCIDRNetworkValidateAttribute( + _ context.Context, strAttr types.String, +) error { + if strAttr.IsNull() || strAttr.IsUnknown() { + return nil + } + + value := strAttr.ValueString() + + _, ipnet, err := net.ParseCIDR(value) + if err != nil { + return err //nolint:wrapcheck + } + if ipnet == nil { + return fmt.Errorf("invalid CIDR: %q", value) + } + + if value != ipnet.String() { + return fmt.Errorf("string is not a CIDR network: %q != %q", value, ipnet) + } + + return nil +} + type StringWildcardNetworkValidator struct{} func StringWildcardNetwork() StringWildcardNetworkValidator { From 712e2462d2b6c8b4648c6adb9bf84872339a32fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 00:26:42 +0000 Subject: [PATCH 11/48] deps: bump github.com/hashicorp/terraform-plugin-framework Bumps [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) from 1.3.4 to 1.3.5. - [Release notes](https://github.com/hashicorp/terraform-plugin-framework/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-framework/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-framework/compare/v1.3.4...v1.3.5) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-framework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 603c6f2c..1d644d84 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-framework v1.3.4 + github.com/hashicorp/terraform-plugin-framework v1.3.5 github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-mux v0.11.2 diff --git a/go.sum b/go.sum index 618dc4d4..fb144229 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980= github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA= github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= -github.com/hashicorp/terraform-plugin-framework v1.3.4 h1:dOTLsALgmQu+PawAvhfGQ04H0MeIz3EZmBw7OFvj7qs= -github.com/hashicorp/terraform-plugin-framework v1.3.4/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= +github.com/hashicorp/terraform-plugin-framework v1.3.5 h1:FJ6s3CVWVAxlhiF/jhy6hzs4AnPHiflsp9KgzTGl1wo= +github.com/hashicorp/terraform-plugin-framework v1.3.5/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 h1:DKb1bX7/EPZUTW6F5zdwJzS/EZ/ycVD6JAW5RYOj4f8= github.com/hashicorp/terraform-plugin-framework-validators v0.11.0/go.mod h1:dzxOiHh7O9CAwc6p8N4mR1H++LtRkl+u+21YNiBVNno= github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= From 07b15d586d623a1a8d699fddff9191d10d58520d Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 18 Aug 2023 13:25:42 +0200 Subject: [PATCH 12/48] r/interface_physical: add non_els arguments - trunk_non_els - vlan_native_non_els --- .changes/issue-521.md | 4 +++ docs/resources/interface_physical.md | 7 +++++ .../resource_interface_physical.go | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 .changes/issue-521.md diff --git a/.changes/issue-521.md b/.changes/issue-521.md new file mode 100644 index 00000000..a1ed1355 --- /dev/null +++ b/.changes/issue-521.md @@ -0,0 +1,4 @@ + +ENHANCEMENTS: + +* **resource/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` arguments (Fix [#521](https://github.com/jeremmfr/terraform-provider-junos/issues/521)) diff --git a/docs/resources/interface_physical.md b/docs/resources/interface_physical.md index c70d24ed..93e37897 100644 --- a/docs/resources/interface_physical.md +++ b/docs/resources/interface_physical.md @@ -112,10 +112,17 @@ The following arguments are supported: Must be a valid speed (10m | 100m | 1g ...) - **trunk** (Optional, Boolean) Interface mode is trunk. +- **trunk_non_els** (Optional, Boolean) + Port mode is trunk. + To use `port-mode` instead of `interface-mode` on non-ELS devices. - **vlan_members** (Optional, List of String) List of vlan for membership for this interface. - **vlan_native** (Optional, Number) Vlan for untagged frames. +- **vlan_native_non_els** (Optional, String) + Vlan for untagged frames. + To use `native-vlan-id` in `unit 0 family ethernet-switching` + instead of interface root level on non-ELS devices. - **vlan_tagging** (Optional, Boolean) Add 802.1q VLAN tagging support. diff --git a/internal/providerfwk/resource_interface_physical.go b/internal/providerfwk/resource_interface_physical.go index 2c27c1f3..a04df0bd 100644 --- a/internal/providerfwk/resource_interface_physical.go +++ b/internal/providerfwk/resource_interface_physical.go @@ -201,6 +201,13 @@ func (rsc *interfacePhysical) Schema( tfvalidator.BoolTrue(), }, }, + "trunk_non_els": schema.BoolAttribute{ + Optional: true, + Description: "Port mode is trunk.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, "vlan_members": schema.ListAttribute{ ElementType: types.StringType, Optional: true, @@ -220,6 +227,14 @@ func (rsc *interfacePhysical) Schema( int64validator.Between(1, 4094), }, }, + "vlan_native_non_els": schema.StringAttribute{ + Optional: true, + Description: "Vlan for untagged frames (non-ELS).", + Validators: []validator.String{ + stringvalidator.LengthBetween(2, 64), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, "vlan_tagging": schema.BoolAttribute{ Optional: true, Description: "Add 802.1q VLAN tagging support.", @@ -615,6 +630,7 @@ type interfacePhysicalData struct { NoGratuitousArpReply types.Bool `tfsdk:"no_gratuitous_arp_reply"` NoGratuitousArpRequest types.Bool `tfsdk:"no_gratuitous_arp_request"` Trunk types.Bool `tfsdk:"trunk"` + TrunkNonELS types.Bool `tfsdk:"trunk_non_els"` VlanTagging types.Bool `tfsdk:"vlan_tagging"` ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` @@ -627,6 +643,7 @@ type interfacePhysicalData struct { Speed types.String `tfsdk:"speed"` VlanMembers []types.String `tfsdk:"vlan_members"` VlanNative types.Int64 `tfsdk:"vlan_native"` + VlanNativeNonELS types.String `tfsdk:"vlan_native_non_els"` ESI *interfacePhysicalBlockESI `tfsdk:"esi"` EtherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"ether_opts"` GigetherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"gigether_opts"` @@ -641,6 +658,7 @@ type interfacePhysicalConfig struct { NoGratuitousArpReply types.Bool `tfsdk:"no_gratuitous_arp_reply"` NoGratuitousArpRequest types.Bool `tfsdk:"no_gratuitous_arp_request"` Trunk types.Bool `tfsdk:"trunk"` + TrunkNonELS types.Bool `tfsdk:"trunk_non_els"` VlanTagging types.Bool `tfsdk:"vlan_tagging"` ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` @@ -653,6 +671,7 @@ type interfacePhysicalConfig struct { Speed types.String `tfsdk:"speed"` VlanMembers types.List `tfsdk:"vlan_members"` VlanNative types.Int64 `tfsdk:"vlan_native"` + VlanNativeNonELS types.String `tfsdk:"vlan_native_non_els"` ESI *interfacePhysicalBlockESI `tfsdk:"esi"` EtherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"ether_opts"` GigetherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"gigether_opts"` @@ -1722,6 +1741,9 @@ func (rscData *interfacePhysicalData) set( if rscData.Trunk.ValueBool() { configSet = append(configSet, setPrefix+"unit 0 family ethernet-switching interface-mode trunk") } + if rscData.TrunkNonELS.ValueBool() { + configSet = append(configSet, setPrefix+"unit 0 family ethernet-switching port-mode trunk") + } for _, v := range rscData.VlanMembers { configSet = append(configSet, setPrefix+ "unit 0 family ethernet-switching vlan members "+v.ValueString()) @@ -1730,6 +1752,9 @@ func (rscData *interfacePhysicalData) set( configSet = append(configSet, setPrefix+"native-vlan-id "+ utils.ConvI64toa(rscData.VlanNative.ValueInt64())) } + if v := rscData.VlanNativeNonELS.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"unit 0 family ethernet-switching native-vlan-id "+v) + } if rscData.VlanTagging.ValueBool() { configSet = append(configSet, setPrefix+"vlan-tagging") } @@ -1996,6 +2021,8 @@ func (rscData *interfacePhysicalData) read( if err != nil { return err } + case balt.CutPrefixInString(&itemTrim, "unit 0 family ethernet-switching native-vlan-id "): + rscData.VlanNativeNonELS = types.StringValue(itemTrim) case itemTrim == "no-gratuitous-arp-reply": rscData.NoGratuitousArpReply = types.BoolValue(true) case itemTrim == "no-gratuitous-arp-request": @@ -2004,6 +2031,8 @@ func (rscData *interfacePhysicalData) read( rscData.Speed = types.StringValue(itemTrim) case itemTrim == "unit 0 family ethernet-switching interface-mode trunk": rscData.Trunk = types.BoolValue(true) + case itemTrim == "unit 0 family ethernet-switching port-mode trunk": + rscData.TrunkNonELS = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "unit 0 family ethernet-switching vlan members "): rscData.VlanMembers = append(rscData.VlanMembers, types.StringValue(itemTrim)) case itemTrim == "vlan-tagging": @@ -2198,6 +2227,8 @@ func (rscData *interfacePhysicalData) delOpts( delPrefix + "redundant-ether-options", delPrefix + "speed", delPrefix + "unit 0 family ethernet-switching interface-mode", + delPrefix + "unit 0 family ethernet-switching port-mode", + delPrefix + "unit 0 family ethernet-switching native-vlan-id", delPrefix + "unit 0 family ethernet-switching vlan members", delPrefix + "vlan-tagging", } From 0d9e01658210d3e726421c9062c139894ec2b68a Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 18 Aug 2023 13:26:21 +0200 Subject: [PATCH 13/48] d/interface_physical: add non_els attributes - trunk_non_els - vlan_native_non_els --- .changes/issue-521.md | 1 + docs/data-sources/interface_physical.md | 7 +++++++ .../providerfwk/data_source_interface_physical.go | 12 ++++++++++++ internal/providerfwk/resource_interface_physical.go | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.changes/issue-521.md b/.changes/issue-521.md index a1ed1355..e6c9b733 100644 --- a/.changes/issue-521.md +++ b/.changes/issue-521.md @@ -2,3 +2,4 @@ ENHANCEMENTS: * **resource/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` arguments (Fix [#521](https://github.com/jeremmfr/terraform-provider-junos/issues/521)) +* **data-source/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` attributes diff --git a/docs/data-sources/interface_physical.md b/docs/data-sources/interface_physical.md index 2449a046..0cb2d59e 100644 --- a/docs/data-sources/interface_physical.md +++ b/docs/data-sources/interface_physical.md @@ -105,10 +105,17 @@ The following attributes are exported: Link speed. - **trunk** (Boolean) Interface mode is trunk. +- **trunk_non_els** (Boolean) + Port mode is trunk. + When use `port-mode` instead of `interface-mode` on non-ELS devices. - **vlan_members** (List of String) List of vlan membership for this interface. - **vlan_native** (Number) Vlan for untagged frames. +- **vlan_native_non_els** (String) + Vlan for untagged frames. + When use `native-vlan-id` in `unit 0 family ethernet-switching` + instead of interface root level on non-ELS devices. - **vlan_tagging** (Boolean) 802.1q VLAN tagging support. diff --git a/internal/providerfwk/data_source_interface_physical.go b/internal/providerfwk/data_source_interface_physical.go index 2dd30eb6..a2510246 100644 --- a/internal/providerfwk/data_source_interface_physical.go +++ b/internal/providerfwk/data_source_interface_physical.go @@ -224,6 +224,10 @@ func (dsc *interfacePhysicalDataSource) Schema( Computed: true, Description: "Interface mode is trunk.", }, + "trunk_non_els": schema.BoolAttribute{ + Computed: true, + Description: "Port mode is trunk.", + }, "vlan_members": schema.ListAttribute{ ElementType: types.StringType, Computed: true, @@ -233,6 +237,10 @@ func (dsc *interfacePhysicalDataSource) Schema( Computed: true, Description: "Vlan for untagged frames.", }, + "vlan_native_non_els": schema.StringAttribute{ + Computed: true, + Description: "Vlan for untagged frames (non-ELS).", + }, "vlan_tagging": schema.BoolAttribute{ Computed: true, Description: "802.1q VLAN tagging support.", @@ -248,6 +256,7 @@ type interfacePhysicalDataSourceData struct { NoGratuitousArpReply types.Bool `tfsdk:"no_gratuitous_arp_reply"` NoGratuitousArpRequest types.Bool `tfsdk:"no_gratuitous_arp_request"` Trunk types.Bool `tfsdk:"trunk"` + TrunkNonELS types.Bool `tfsdk:"trunk_non_els"` VlanTagging types.Bool `tfsdk:"vlan_tagging"` ID types.String `tfsdk:"id"` ConfigInterface types.String `tfsdk:"config_interface"` @@ -262,6 +271,7 @@ type interfacePhysicalDataSourceData struct { Speed types.String `tfsdk:"speed"` VlanMembers []types.String `tfsdk:"vlan_members"` VlanNative types.Int64 `tfsdk:"vlan_native"` + VlanNativeNonELS types.String `tfsdk:"vlan_native_non_els"` ESI *interfacePhysicalBlockESI `tfsdk:"esi"` EtherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"ether_opts"` GigetherOpts *interfacePhysicalBlockEtherOpts `tfsdk:"gigether_opts"` @@ -411,7 +421,9 @@ func (dscData *interfacePhysicalDataSourceData) CopyFromResourceData(rscData int dscData.ParentEtherOpts = rscData.ParentEtherOpts dscData.Speed = rscData.Speed dscData.Trunk = rscData.Trunk + dscData.TrunkNonELS = rscData.TrunkNonELS dscData.VlanMembers = rscData.VlanMembers dscData.VlanNative = rscData.VlanNative + dscData.VlanNativeNonELS = rscData.VlanNativeNonELS dscData.VlanTagging = rscData.VlanTagging } diff --git a/internal/providerfwk/resource_interface_physical.go b/internal/providerfwk/resource_interface_physical.go index a04df0bd..fe3114d9 100644 --- a/internal/providerfwk/resource_interface_physical.go +++ b/internal/providerfwk/resource_interface_physical.go @@ -2227,8 +2227,8 @@ func (rscData *interfacePhysicalData) delOpts( delPrefix + "redundant-ether-options", delPrefix + "speed", delPrefix + "unit 0 family ethernet-switching interface-mode", - delPrefix + "unit 0 family ethernet-switching port-mode", delPrefix + "unit 0 family ethernet-switching native-vlan-id", + delPrefix + "unit 0 family ethernet-switching port-mode", delPrefix + "unit 0 family ethernet-switching vlan members", delPrefix + "vlan-tagging", } From cfc160f48ff573b707b9bcb0701a5e6950a56268 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Aug 2023 19:10:41 +0200 Subject: [PATCH 14/48] tests: fix version golangci-lint to v1.54.2 and disable gosec generating new odd issue with this new patch from golangci-lint --- .github/workflows/linters.yml | 2 +- .golangci.yml | 4 +--- internal/junos/constants.go | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index b2047bf5..e8e1ff00 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.54 + version: 'v1.54.2' args: -c .golangci.yml -v markdown-lint: diff --git a/.golangci.yml b/.golangci.yml index 06ff9ce5..14ce14bb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -30,6 +30,7 @@ linters: - nosnakecase - musttag - depguard + - gosec linters-settings: gci: custom-order: true @@ -59,9 +60,6 @@ linters-settings: - shadow issues: exclude-rules: - - text: "Use of ssh InsecureIgnoreHostKey" - linters: - - gosec - text: "github.com/jeremmfr/terraform-provider-junos/internal" linters: - wrapcheck diff --git a/internal/junos/constants.go b/internal/junos/constants.go index 3b804581..7b1cdab2 100644 --- a/internal/junos/constants.go +++ b/internal/junos/constants.go @@ -39,7 +39,7 @@ const ( EnvPassword = "JUNOS_PASSWORD" EnvKeyPem = "JUNOS_KEYPEM" EnvKeyFile = "JUNOS_KEYFILE" - EnvKeyPass = "JUNOS_KEYPASS" //nolint:gosec + EnvKeyPass = "JUNOS_KEYPASS" EnvGroupInterfaceDelete = "JUNOS_GROUP_INTERFACE_DELETE" EnvSleepShort = "JUNOS_SLEEP_SHORT" EnvSleepLock = "JUNOS_SLEEP_LOCK" From 6a2610d4b25a20a31ed451d57c40180d77c9ff0e Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 21 Aug 2023 20:09:45 +0200 Subject: [PATCH 15/48] workflows: remove Linters-Main --- .github/workflows/linters-main.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/linters-main.yml diff --git a/.github/workflows/linters-main.yml b/.github/workflows/linters-main.yml deleted file mode 100644 index eadd7dbf..00000000 --- a/.github/workflows/linters-main.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Linters-Main -on: - push: - branches: - - main -jobs: - golangci-lint: - name: golangci-lint-latest - runs-on: ubuntu-latest - steps: - - name: Set up Go 1.20 - uses: actions/setup-go@v4 - with: - go-version: '1.20' - check-latest: true - id: go - - name: Disable cgo - run: | - echo "CGO_ENABLED=0" >> $GITHUB_ENV - - name: Check out code - uses: actions/checkout@v3 - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: latest - args: -c .golangci.yml -v From de2b87bc18ebd752602b7038150d7a5500051046 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 10 Aug 2023 09:03:35 +0200 Subject: [PATCH 16/48] bump Go version to v1.20 minimum and release with v1.21 --- .changes/go-1.21.md | 4 ++++ .github/workflows/go.yml | 20 ++++++++++---------- .github/workflows/go_analysis.yml | 2 +- .github/workflows/linters.yml | 8 ++++---- .github/workflows/releases.yml | 4 ++-- README.md | 2 +- go.mod | 2 +- 7 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 .changes/go-1.21.md diff --git a/.changes/go-1.21.md b/.changes/go-1.21.md new file mode 100644 index 00000000..6fe2502d --- /dev/null +++ b/.changes/go-1.21.md @@ -0,0 +1,4 @@ + +ENHANCEMENTS: + +* release now with golang 1.21 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e2024da3..4a6d630a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,14 +1,14 @@ name: Go Tests on: [push, pull_request] jobs: - build-1_19: - name: Build 1.19 + build-1_20: + name: Build 1.20 runs-on: ubuntu-latest steps: - - name: Set up Go 1.19 + - name: Set up Go 1.20 uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: '1.20' check-latest: true id: go - name: Disable cgo @@ -21,14 +21,14 @@ jobs: - name: Build run: go build -v . - build-1_20: - name: Build 1.20 + build-1_21: + name: Build 1.21 runs-on: ubuntu-latest steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' check-latest: true id: go - name: Disable cgo @@ -45,10 +45,10 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' check-latest: true id: go - name: Disable cgo diff --git a/.github/workflows/go_analysis.yml b/.github/workflows/go_analysis.yml index 18d050ff..39a516c7 100644 --- a/.github/workflows/go_analysis.yml +++ b/.github/workflows/go_analysis.yml @@ -16,7 +16,7 @@ jobs: - name: Running govulncheck uses: Templum/govulncheck-action@v1.0.0 with: - go-version: '1.20' + go-version: '1.21' package: ./... fail-on-vuln: false diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index e8e1ff00..ce36f440 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -5,10 +5,10 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' check-latest: true id: go - name: Disable cgo @@ -38,10 +38,10 @@ jobs: name: terrafmt runs-on: ubuntu-latest steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' check-latest: true id: go - name: Show version diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 792c4120..b7aebc4b 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -38,10 +38,10 @@ jobs: - goos: windows goarch: arm64 steps: - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '^1.20.4' + go-version: '^1.21.0' check-latest: true id: go - name: Show version diff --git a/README.md b/README.md index b16d6cfa..a9b86c39 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ for provider and resources documentation. ### In addition to develop -- [Go](https://golang.org/doc/install) `v1.19` or `v1.20` +- [Go](https://golang.org/doc/install) `v1.20` or `v1.21` ## Automatic install diff --git a/go.mod b/go.mod index 1d644d84..f35e0943 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jeremmfr/terraform-provider-junos -go 1.19 +go 1.20 require ( github.com/google/go-cmp v0.5.9 From d5ea4491f422122e4fcf1f08c85c9cce08fb19f7 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 23 Aug 2023 09:36:17 +0200 Subject: [PATCH 17/48] internal: delete unused constants in junos --- internal/junos/constants.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/junos/constants.go b/internal/junos/constants.go index 7b1cdab2..8c2aa4da 100644 --- a/internal/junos/constants.go +++ b/internal/junos/constants.go @@ -24,10 +24,9 @@ const ( St0Word = "st0" - EvpnW = "evpn" InetW = "inet" Inet6W = "inet6" - MplsW = "mpls" + OspfV2 = "ospf" OspfV3 = "ospf3" From 1d8157fc9016fd668814022ca9f80896d9d45be4 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 23 Aug 2023 09:46:27 +0200 Subject: [PATCH 18/48] r/policyoptions_policy_statement: fix path when catch error in configSet with multiple community blocks with the same argument values in then block --- internal/providerfwk/resource_policyoptions_policy_statement.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providerfwk/resource_policyoptions_policy_statement.go b/internal/providerfwk/resource_policyoptions_policy_statement.go index cfa6657b..bd7fca8a 100644 --- a/internal/providerfwk/resource_policyoptions_policy_statement.go +++ b/internal/providerfwk/resource_policyoptions_policy_statement.go @@ -2446,7 +2446,7 @@ func (block *policyoptionsPolicyStatementBlockThen) configSet( values := v.Action.ValueString() + " " + v.Value.ValueString() if _, ok := communityBlock[values]; ok { return configSet, - pathRoot.AtName("community").AtListIndex(i), + pathRoot.AtName("community").AtListIndex(i).AtName("action"), fmt.Errorf("multiple community blocks with the same argument values %q in then block", values) } communityBlock[values] = struct{}{} From ae8cdfead7dc809d6f13c988c8ab9b9b4376bc8c Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 24 Aug 2023 13:59:15 +0200 Subject: [PATCH 19/48] r/security_nat_static_rule: tests: avoid potential prefix overlaps between rules --- .../resource_security_nat_static_rule_test.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/providerfwk/resource_security_nat_static_rule_test.go b/internal/providerfwk/resource_security_nat_static_rule_test.go index 2ea6bbb6..a52b27f3 100644 --- a/internal/providerfwk/resource_security_nat_static_rule_test.go +++ b/internal/providerfwk/resource_security_nat_static_rule_test.go @@ -19,22 +19,22 @@ func TestAccJunosSecurityNatStaticRule_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", "name", "testacc_securityNATSttRule"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "destination_address", "192.0.2.0/25"), + "destination_address", "192.0.2.0/28"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", "then.type", "prefix"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", "then.routing_instance", "testacc_securityNATSttRule"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.prefix", "192.0.2.128/25"), + "then.prefix", "192.0.2.128/28"), ), }, { Config: testAccJunosSecurityNatStaticRuleConfigUpdate(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "destination_address", "192.0.2.0/26"), + "destination_address", "192.0.2.0/27"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule", - "then.prefix", "192.0.2.64/26"), + "then.prefix", "192.0.2.64/27"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule2", "destination_address_name", "testacc_securityNATSttRule2"), resource.TestCheckResourceAttr("junos_security_nat_static_rule.testacc_securityNATSttRule2", @@ -79,11 +79,11 @@ resource "junos_security_nat_static" "testacc_securityNATSttRule" { resource "junos_security_nat_static_rule" "testacc_securityNATSttRule" { name = "testacc_securityNATSttRule" rule_set = junos_security_nat_static.testacc_securityNATSttRule.name - destination_address = "192.0.2.0/25" + destination_address = "192.0.2.0/28" then { type = "prefix" routing_instance = junos_routing_instance.testacc_securityNATSttRule.name - prefix = "192.0.2.128/25" + prefix = "192.0.2.128/28" } } resource "junos_security_nat_static_rule" "testacc_securityNATSttRuleInet" { @@ -117,11 +117,11 @@ resource "junos_security_nat_static" "testacc_securityNATSttRule" { resource "junos_security_nat_static_rule" "testacc_securityNATSttRule" { name = "testacc_securityNATSttRule" rule_set = junos_security_nat_static.testacc_securityNATSttRule.name - destination_address = "192.0.2.0/26" + destination_address = "192.0.2.0/27" then { type = "prefix" routing_instance = junos_routing_instance.testacc_securityNATSttRule.name - prefix = "192.0.2.64/26" + prefix = "192.0.2.64/27" } } resource "junos_security_nat_static_rule" "testacc_securityNATSttRule2" { @@ -132,7 +132,7 @@ resource "junos_security_nat_static_rule" "testacc_securityNATSttRule2" { rule_set = junos_security_nat_static.testacc_securityNATSttRule.name destination_address_name = "testacc_securityNATSttRule2" source_address = [ - "192.0.2.128/26" + "192.0.2.144/28" ] source_port = [ "1024", @@ -174,11 +174,11 @@ resource "junos_routing_instance" "testacc_securityNATSttRule" { resource "junos_security_address_book" "testacc_securityNATSttRule" { network_address { name = "testacc_securityNATSttRule2" - value = "192.0.2.128/27" + value = "192.0.2.160/28" } network_address { name = "testacc_securityNATSttRule-prefix" - value = "192.0.2.160/27" + value = "192.0.2.176/28" } network_address { name = "testacc_securityNATSttRule-src" From 44709b89d3d34f389b2eb7c8e80633f6d981c9b3 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 09:32:51 +0200 Subject: [PATCH 20/48] r/*: fix missing to use the constant for the ID separator in providerfwk --- internal/providerfwk/resource_bgp_group.go | 2 +- internal/providerfwk/resource_bgp_neighbor.go | 5 +++-- internal/providerfwk/resource_firewall_filter.go | 2 +- .../resource_forwardingoptions_sampling_instance.go | 2 +- internal/providerfwk/resource_security_nat_static_rule.go | 4 ++-- internal/providerfwk/resource_security_nat_static_test.go | 4 +++- internal/providerfwk/resource_security_policy.go | 2 +- .../resource_security_policy_tunnel_pair_policy.go | 4 +++- internal/providerfwk/resource_security_zone_book_address.go | 2 +- .../providerfwk/resource_security_zone_book_address_set.go | 2 +- 10 files changed, 17 insertions(+), 12 deletions(-) diff --git a/internal/providerfwk/resource_bgp_group.go b/internal/providerfwk/resource_bgp_group.go index 213d7880..ee861cb3 100644 --- a/internal/providerfwk/resource_bgp_group.go +++ b/internal/providerfwk/resource_bgp_group.go @@ -88,7 +88,7 @@ func (rsc *bgpGroup) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_bgp_neighbor.go b/internal/providerfwk/resource_bgp_neighbor.go index e7cecd5e..024bb972 100644 --- a/internal/providerfwk/resource_bgp_neighbor.go +++ b/internal/providerfwk/resource_bgp_neighbor.go @@ -87,8 +87,9 @@ func (rsc *bgpNeighbor) Schema( Description: "Provides a " + rsc.junosName() + ".", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Computed: true, - Description: "An identifier for the resource with format `_-__-_`.", + Computed: true, + Description: "An identifier for the resource with format " + + "`" + junos.IDSeparator + "" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_firewall_filter.go b/internal/providerfwk/resource_firewall_filter.go index af3a2d07..c3dd758a 100644 --- a/internal/providerfwk/resource_firewall_filter.go +++ b/internal/providerfwk/resource_firewall_filter.go @@ -86,7 +86,7 @@ func (rsc *firewallFilter) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_forwardingoptions_sampling_instance.go b/internal/providerfwk/resource_forwardingoptions_sampling_instance.go index 5fa1cfde..6f367641 100644 --- a/internal/providerfwk/resource_forwardingoptions_sampling_instance.go +++ b/internal/providerfwk/resource_forwardingoptions_sampling_instance.go @@ -86,7 +86,7 @@ func (rsc *forwardingoptionsSamplingInstance) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_security_nat_static_rule.go b/internal/providerfwk/resource_security_nat_static_rule.go index d42e460f..363957bd 100644 --- a/internal/providerfwk/resource_security_nat_static_rule.go +++ b/internal/providerfwk/resource_security_nat_static_rule.go @@ -86,7 +86,7 @@ func (rsc *securityNatStaticRule) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -552,7 +552,7 @@ func (rsc *securityNatStaticRule) ImportState( req, resp, fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ - "(id must be _-_)", req.ID), + "(id must be "+junos.IDSeparator+")", req.ID), ) } diff --git a/internal/providerfwk/resource_security_nat_static_test.go b/internal/providerfwk/resource_security_nat_static_test.go index a270edc4..33338e00 100644 --- a/internal/providerfwk/resource_security_nat_static_test.go +++ b/internal/providerfwk/resource_security_nat_static_test.go @@ -4,6 +4,8 @@ import ( "os" "testing" + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -61,7 +63,7 @@ func TestAccJunosSecurityNatStatic_basic(t *testing.T) { { ResourceName: "junos_security_nat_static.testacc_securityNATStt_singly", ImportState: true, - ImportStateId: "testacc_securityNATStt_singly_-_no_rules", + ImportStateId: "testacc_securityNATStt_singly" + junos.IDSeparator + "no_rules", }, { Config: testAccJunosSecurityNatStaticConfigUpdate2(), diff --git a/internal/providerfwk/resource_security_policy.go b/internal/providerfwk/resource_security_policy.go index cfe1c514..65270abc 100644 --- a/internal/providerfwk/resource_security_policy.go +++ b/internal/providerfwk/resource_security_policy.go @@ -84,7 +84,7 @@ func (rsc *securityPolicy) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_security_policy_tunnel_pair_policy.go b/internal/providerfwk/resource_security_policy_tunnel_pair_policy.go index 22cd6677..34746fcc 100644 --- a/internal/providerfwk/resource_security_policy_tunnel_pair_policy.go +++ b/internal/providerfwk/resource_security_policy_tunnel_pair_policy.go @@ -77,7 +77,9 @@ func (rsc *securityPolicyTunnelPairPolicy) Schema( "id": schema.StringAttribute{ Computed: true, Description: "An identifier for the resource with format " + - "`_-__-__-_`.", + "`" + junos.IDSeparator + "" + + junos.IDSeparator + + "" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_security_zone_book_address.go b/internal/providerfwk/resource_security_zone_book_address.go index 9221b4f1..7e4b354c 100644 --- a/internal/providerfwk/resource_security_zone_book_address.go +++ b/internal/providerfwk/resource_security_zone_book_address.go @@ -78,7 +78,7 @@ func (rsc *securityZoneBookAddress) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, diff --git a/internal/providerfwk/resource_security_zone_book_address_set.go b/internal/providerfwk/resource_security_zone_book_address_set.go index 19f14f9e..33143b3c 100644 --- a/internal/providerfwk/resource_security_zone_book_address_set.go +++ b/internal/providerfwk/resource_security_zone_book_address_set.go @@ -79,7 +79,7 @@ func (rsc *securityZoneBookAddressSet) Schema( Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, - Description: "An identifier for the resource with format `_-_`.", + Description: "An identifier for the resource with format `" + junos.IDSeparator + "`.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, From ac1b8f766cd5f571b38b5d78bc761eecd0122e0e Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:30:19 +0200 Subject: [PATCH 21/48] r/bgp_group: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../providerfwk/upgradestate_bgp_group.go | 142 ++++++++++++------ 1 file changed, 92 insertions(+), 50 deletions(-) diff --git a/internal/providerfwk/upgradestate_bgp_group.go b/internal/providerfwk/upgradestate_bgp_group.go index 66e98f6a..09f2572d 100644 --- a/internal/providerfwk/upgradestate_bgp_group.go +++ b/internal/providerfwk/upgradestate_bgp_group.go @@ -299,52 +299,69 @@ 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 { + 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 []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"` + } `tfsdk:"bfd_liveness_detection"` + BgpMultipath []struct { + AllowProtection types.Bool `tfsdk:"allow_protection"` + Disable types.Bool `tfsdk:"disable"` + MultipleAS types.Bool `tfsdk:"multiple_as"` + } `tfsdk:"bgp_multipath"` + FamilyEvpn []struct { NlriType types.String `tfsdk:"nlri_type"` AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` @@ -359,7 +376,11 @@ func upgradeBgpGroupStateV0toV1( AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` } `tfsdk:"family_inet6"` - GracefulRestart []bgpBlockGracefulRestart `tfsdk:"graceful_restart"` + GracefulRestart []struct { + Disable types.Bool `tfsdk:"disable"` + RestartTime types.Int64 `tfsdk:"restart_time"` + StaleRouteTime types.Int64 `tfsdk:"stale_route_time"` + } `tfsdk:"graceful_restart"` } var dataV0 modelV0 @@ -413,10 +434,27 @@ func upgradeBgpGroupStateV0toV1( dataV1.Preference = dataV0.Preference dataV1.RemovePrivate = dataV0.RemovePrivate if len(dataV0.BfdLivenessDetection) > 0 { - dataV1.BfdLivenessDetection = &dataV0.BfdLivenessDetection[0] + dataV1.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{ + AuthenticationLooseCheck: dataV0.BfdLivenessDetection[0].AuthenticationLooseCheck, + AuthenticationAlgorithm: dataV0.BfdLivenessDetection[0].AuthenticationAlgorithm, + AuthenticationKeyChain: dataV0.BfdLivenessDetection[0].AuthenticationKeyChain, + DetectionTimeThreshold: dataV0.BfdLivenessDetection[0].DetectionTimeThreshold, + HolddownInterval: dataV0.BfdLivenessDetection[0].HolddownInterval, + MinimumInterval: dataV0.BfdLivenessDetection[0].MinimumInterval, + MinimumReceiveInterval: dataV0.BfdLivenessDetection[0].MinimumReceiveInterval, + Multiplier: dataV0.BfdLivenessDetection[0].Multiplier, + SessionMode: dataV0.BfdLivenessDetection[0].SessionMode, + TransmitIntervalMinimumInterval: dataV0.BfdLivenessDetection[0].TransmitIntervalMinimumInterval, + TransmitIntervalThreshold: dataV0.BfdLivenessDetection[0].TransmitIntervalThreshold, + Version: dataV0.BfdLivenessDetection[0].Version, + } } if len(dataV0.BgpMultipath) > 0 { - dataV1.BgpMultipath = &dataV0.BgpMultipath[0] + dataV1.BgpMultipath = &bgpBlockBgpMultipath{ + AllowProtection: dataV0.BgpMultipath[0].AllowProtection, + Disable: dataV0.BgpMultipath[0].Disable, + MultipleAS: dataV0.BgpMultipath[0].MultipleAS, + } } for _, blockV0 := range dataV0.FamilyEvpn { blockV1 := bgpBlockFamily{ @@ -455,7 +493,11 @@ func upgradeBgpGroupStateV0toV1( dataV1.FamilyInet6 = append(dataV1.FamilyInet6, blockV1) } if len(dataV0.GracefulRestart) > 0 { - dataV1.GracefulRestart = &dataV0.GracefulRestart[0] + dataV1.GracefulRestart = &bgpBlockGracefulRestart{ + Disable: dataV0.GracefulRestart[0].Disable, + RestartTime: dataV0.GracefulRestart[0].RestartTime, + StaleRouteTime: dataV0.GracefulRestart[0].StaleRouteTime, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From 4120fbe0a034b82fec9fd9b45611c322b43bc34c Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:31:07 +0200 Subject: [PATCH 22/48] r/bgp_neighbor: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../providerfwk/upgradestate_bgp_neighbor.go | 142 ++++++++++++------ 1 file changed, 92 insertions(+), 50 deletions(-) diff --git a/internal/providerfwk/upgradestate_bgp_neighbor.go b/internal/providerfwk/upgradestate_bgp_neighbor.go index 4fdbc133..a3ad0691 100644 --- a/internal/providerfwk/upgradestate_bgp_neighbor.go +++ b/internal/providerfwk/upgradestate_bgp_neighbor.go @@ -298,52 +298,69 @@ 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 { + 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 []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"` + } `tfsdk:"bfd_liveness_detection"` + BgpMultipath []struct { + AllowProtection types.Bool `tfsdk:"allow_protection"` + Disable types.Bool `tfsdk:"disable"` + MultipleAS types.Bool `tfsdk:"multiple_as"` + } `tfsdk:"bgp_multipath"` + FamilyEvpn []struct { NlriType types.String `tfsdk:"nlri_type"` AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` @@ -358,7 +375,11 @@ func upgradeBgpNeighborStateV0toV1( AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` } `tfsdk:"family_inet6"` - GracefulRestart []bgpBlockGracefulRestart `tfsdk:"graceful_restart"` + GracefulRestart []struct { + Disable types.Bool `tfsdk:"disable"` + RestartTime types.Int64 `tfsdk:"restart_time"` + StaleRouteTime types.Int64 `tfsdk:"stale_route_time"` + } `tfsdk:"graceful_restart"` } var dataV0 modelV0 @@ -412,10 +433,27 @@ func upgradeBgpNeighborStateV0toV1( dataV1.Preference = dataV0.Preference dataV1.RemovePrivate = dataV0.RemovePrivate if len(dataV0.BfdLivenessDetection) > 0 { - dataV1.BfdLivenessDetection = &dataV0.BfdLivenessDetection[0] + dataV1.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{ + AuthenticationLooseCheck: dataV0.BfdLivenessDetection[0].AuthenticationLooseCheck, + AuthenticationAlgorithm: dataV0.BfdLivenessDetection[0].AuthenticationAlgorithm, + AuthenticationKeyChain: dataV0.BfdLivenessDetection[0].AuthenticationKeyChain, + DetectionTimeThreshold: dataV0.BfdLivenessDetection[0].DetectionTimeThreshold, + HolddownInterval: dataV0.BfdLivenessDetection[0].HolddownInterval, + MinimumInterval: dataV0.BfdLivenessDetection[0].MinimumInterval, + MinimumReceiveInterval: dataV0.BfdLivenessDetection[0].MinimumReceiveInterval, + Multiplier: dataV0.BfdLivenessDetection[0].Multiplier, + SessionMode: dataV0.BfdLivenessDetection[0].SessionMode, + TransmitIntervalMinimumInterval: dataV0.BfdLivenessDetection[0].TransmitIntervalMinimumInterval, + TransmitIntervalThreshold: dataV0.BfdLivenessDetection[0].TransmitIntervalThreshold, + Version: dataV0.BfdLivenessDetection[0].Version, + } } if len(dataV0.BgpMultipath) > 0 { - dataV1.BgpMultipath = &dataV0.BgpMultipath[0] + dataV1.BgpMultipath = &bgpBlockBgpMultipath{ + AllowProtection: dataV0.BgpMultipath[0].AllowProtection, + Disable: dataV0.BgpMultipath[0].Disable, + MultipleAS: dataV0.BgpMultipath[0].MultipleAS, + } } for _, blockV0 := range dataV0.FamilyEvpn { blockV1 := bgpBlockFamily{ @@ -454,7 +492,11 @@ func upgradeBgpNeighborStateV0toV1( dataV1.FamilyInet6 = append(dataV1.FamilyInet6, blockV1) } if len(dataV0.GracefulRestart) > 0 { - dataV1.GracefulRestart = &dataV0.GracefulRestart[0] + dataV1.GracefulRestart = &bgpBlockGracefulRestart{ + Disable: dataV0.GracefulRestart[0].Disable, + RestartTime: dataV0.GracefulRestart[0].RestartTime, + StaleRouteTime: dataV0.GracefulRestart[0].StaleRouteTime, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From 16a5ef7fcc84908782b5461a0c45cbce280f2f0c Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:31:53 +0200 Subject: [PATCH 23/48] r/firewall_policer: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_firewall_policer.go | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/providerfwk/upgradestate_firewall_policer.go b/internal/providerfwk/upgradestate_firewall_policer.go index 4c70b64a..ed06dee2 100644 --- a/internal/providerfwk/upgradestate_firewall_policer.go +++ b/internal/providerfwk/upgradestate_firewall_policer.go @@ -68,11 +68,20 @@ func upgradeFirewallPolicerStateV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - FilterSpecific types.Bool `tfsdk:"filter_specific"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - IfExceeding []firewallPolicerBlockIfExceeding `tfsdk:"if_exceeding"` - Then []firewallPolicerBlockThen `tfsdk:"then"` + FilterSpecific types.Bool `tfsdk:"filter_specific"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + IfExceeding []struct { + BurstSizeLimit types.String `tfsdk:"burst_size_limit"` + BandwidthPercent types.Int64 `tfsdk:"bandwidth_percent"` + BandwidthLimit types.String `tfsdk:"bandwidth_limit"` + } `tfsdk:"if_exceeding"` + Then []struct { + Discard types.Bool `tfsdk:"discard"` + OutOfProfile types.Bool `tfsdk:"out_of_profile"` + ForwardingClass types.String `tfsdk:"forwarding_class"` + LossPriority types.String `tfsdk:"loss_priority"` + } `tfsdk:"then"` } var dataV0 modelV0 @@ -86,10 +95,19 @@ func upgradeFirewallPolicerStateV0toV1( dataV1.Name = dataV0.Name dataV1.FilterSpecific = dataV0.FilterSpecific if len(dataV0.IfExceeding) > 0 { - dataV1.IfExceeding = &dataV0.IfExceeding[0] + dataV1.IfExceeding = &firewallPolicerBlockIfExceeding{ + BurstSizeLimit: dataV0.IfExceeding[0].BurstSizeLimit, + BandwidthPercent: dataV0.IfExceeding[0].BandwidthPercent, + BandwidthLimit: dataV0.IfExceeding[0].BandwidthLimit, + } } if len(dataV0.Then) > 0 { - dataV1.Then = &dataV0.Then[0] + dataV1.Then = &firewallPolicerBlockThen{ + Discard: dataV0.Then[0].Discard, + OutOfProfile: dataV0.Then[0].OutOfProfile, + ForwardingClass: dataV0.Then[0].ForwardingClass, + LossPriority: dataV0.Then[0].LossPriority, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From a229d44486cd4d41f6a2e01916c7db47bcf16756 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:32:48 +0200 Subject: [PATCH 24/48] r/forwardingoptions_sampling_instance: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- ...ate_forwardingoptions_sampling_instance.go | 483 +++++++++++++++++- 1 file changed, 458 insertions(+), 25 deletions(-) diff --git a/internal/providerfwk/upgradestate_forwardingoptions_sampling_instance.go b/internal/providerfwk/upgradestate_forwardingoptions_sampling_instance.go index 112e1f42..857add78 100644 --- a/internal/providerfwk/upgradestate_forwardingoptions_sampling_instance.go +++ b/internal/providerfwk/upgradestate_forwardingoptions_sampling_instance.go @@ -26,12 +26,45 @@ func (rsc *forwardingoptionsSamplingInstance) UpgradeState(_ context.Context) ma Blocks: map[string]schema.Block{ "family_inet_input": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaInputAttributes(), + Attributes: map[string]schema.Attribute{ + "max_packets_per_second": schema.Int64Attribute{ + Optional: true, + }, + "maximum_packet_length": schema.Int64Attribute{ + Optional: true, + }, + "rate": schema.Int64Attribute{ + Optional: true, + }, + "run_length": schema.Int64Attribute{ + Optional: true, + }, + }, }, }, "family_inet_output": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaFamilyInetOutputAttributes(), + Attributes: map[string]schema.Attribute{ + "aggregate_export_interval": schema.Int64Attribute{ + Optional: true, + }, + "extension_service": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "flow_active_timeout": schema.Int64Attribute{ + Optional: true, + }, + "flow_inactive_timeout": schema.Int64Attribute{ + Optional: true, + }, + "inline_jflow_export_rate": schema.Int64Attribute{ + Optional: true, + }, + "inline_jflow_source_address": schema.StringAttribute{ + Optional: true, + }, + }, Blocks: map[string]schema.Block{ "flow_server": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ @@ -103,18 +136,127 @@ func (rsc *forwardingoptionsSamplingInstance) UpgradeState(_ context.Context) ma }, "family_inet6_input": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaInputAttributes(), + Attributes: map[string]schema.Attribute{ + "max_packets_per_second": schema.Int64Attribute{ + Optional: true, + }, + "maximum_packet_length": schema.Int64Attribute{ + Optional: true, + }, + "rate": schema.Int64Attribute{ + Optional: true, + }, + "run_length": schema.Int64Attribute{ + Optional: true, + }, + }, }, }, "family_inet6_output": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaFamilyInetOutputAttributes(), - Blocks: rsc.schemaOutputBlock(), + Attributes: map[string]schema.Attribute{ + "aggregate_export_interval": schema.Int64Attribute{ + Optional: true, + }, + "extension_service": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "flow_active_timeout": schema.Int64Attribute{ + Optional: true, + }, + "flow_inactive_timeout": schema.Int64Attribute{ + Optional: true, + }, + "inline_jflow_export_rate": schema.Int64Attribute{ + Optional: true, + }, + "inline_jflow_source_address": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "flow_server": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "hostname": schema.StringAttribute{ + Required: true, + }, + "port": schema.Int64Attribute{ + Required: true, + }, + "aggregation_autonomous_system": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_destination_prefix": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_protocol_port": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_destination_prefix": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_destination_prefix_caida_compliant": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_prefix": schema.BoolAttribute{ + Optional: true, + }, + "autonomous_system_type": schema.StringAttribute{ + Optional: true, + }, + "dscp": schema.Int64Attribute{ + Optional: true, + }, + "forwarding_class": schema.StringAttribute{ + Optional: true, + }, + "local_dump": schema.BoolAttribute{ + Optional: true, + }, + "no_local_dump": schema.BoolAttribute{ + Optional: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + }, + "source_address": schema.StringAttribute{ + Optional: true, + }, + "version9_template": schema.StringAttribute{ + Optional: true, + }, + "version_ipfix_template": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "interface": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaOutputInterfaceAttributes(), + }, + }, + }, }, }, "family_mpls_input": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaInputAttributes(), + Attributes: map[string]schema.Attribute{ + "max_packets_per_second": schema.Int64Attribute{ + Optional: true, + }, + "maximum_packet_length": schema.Int64Attribute{ + Optional: true, + }, + "rate": schema.Int64Attribute{ + Optional: true, + }, + "run_length": schema.Int64Attribute{ + Optional: true, + }, + }, }, }, "family_mpls_output": schema.ListNestedBlock{ @@ -136,12 +278,88 @@ func (rsc *forwardingoptionsSamplingInstance) UpgradeState(_ context.Context) ma Optional: true, }, }, - Blocks: rsc.schemaOutputBlock(), + Blocks: map[string]schema.Block{ + "flow_server": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "hostname": schema.StringAttribute{ + Required: true, + }, + "port": schema.Int64Attribute{ + Required: true, + }, + "aggregation_autonomous_system": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_destination_prefix": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_protocol_port": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_destination_prefix": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_destination_prefix_caida_compliant": schema.BoolAttribute{ + Optional: true, + }, + "aggregation_source_prefix": schema.BoolAttribute{ + Optional: true, + }, + "autonomous_system_type": schema.StringAttribute{ + Optional: true, + }, + "dscp": schema.Int64Attribute{ + Optional: true, + }, + "forwarding_class": schema.StringAttribute{ + Optional: true, + }, + "local_dump": schema.BoolAttribute{ + Optional: true, + }, + "no_local_dump": schema.BoolAttribute{ + Optional: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + }, + "source_address": schema.StringAttribute{ + Optional: true, + }, + "version9_template": schema.StringAttribute{ + Optional: true, + }, + "version_ipfix_template": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "interface": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaOutputInterfaceAttributes(), + }, + }, + }, }, }, "input": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaInputAttributes(), + Attributes: map[string]schema.Attribute{ + "max_packets_per_second": schema.Int64Attribute{ + Optional: true, + }, + "maximum_packet_length": schema.Int64Attribute{ + Optional: true, + }, + "rate": schema.Int64Attribute{ + Optional: true, + }, + "run_length": schema.Int64Attribute{ + Optional: true, + }, + }, }, }, }, @@ -154,17 +372,119 @@ func (rsc *forwardingoptionsSamplingInstance) UpgradeState(_ context.Context) ma func upgradeForwardingoptionsSamplingInstanceStateV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { + //nolint:lll type modelV0 struct { - Disable types.Bool `tfsdk:"disable"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - FamilyInetInput []forwardingoptionsSamplingInstanceBlockInput `tfsdk:"family_inet_input"` - FamilyInetOutput []forwardingoptionsSamplingInstanceBlockFamilyInetOutput `tfsdk:"family_inet_output"` - FamilyInet6Input []forwardingoptionsSamplingInstanceBlockInput `tfsdk:"family_inet6_input"` - FamilyInet6Output []forwardingoptionsSamplingInstanceBlockFamilyInet6Output `tfsdk:"family_inet6_output"` - FamilyMplsInput []forwardingoptionsSamplingInstanceBlockInput `tfsdk:"family_mpls_input"` - FamilyMplsOutput []forwardingoptionsSamplingInstanceBlockFamilyMplsOutput `tfsdk:"family_mpls_output"` - Input []forwardingoptionsSamplingInstanceBlockInput `tfsdk:"input"` + Disable types.Bool `tfsdk:"disable"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + FamilyInetInput []struct { + MaxPacketsPerSecond types.Int64 `tfsdk:"max_packets_per_second"` + MaximumPacketLength types.Int64 `tfsdk:"maximum_packet_length"` + Rate types.Int64 `tfsdk:"rate"` + RunLength types.Int64 `tfsdk:"run_length"` + } `tfsdk:"family_inet_input"` + FamilyInetOutput []struct { + AggregateExportInterval types.Int64 `tfsdk:"aggregate_export_interval"` + ExtensionService []types.String `tfsdk:"extension_service"` + FlowActiveTimeout types.Int64 `tfsdk:"flow_active_timeout"` + FlowInactiveTimeout types.Int64 `tfsdk:"flow_inactive_timeout"` + InlineJflowExportRate types.Int64 `tfsdk:"inline_jflow_export_rate"` + InlineJflowSourceAddress types.String `tfsdk:"inline_jflow_source_address"` + FlowServer []struct { + AggregationAutonomousSystem types.Bool `tfsdk:"aggregation_autonomous_system"` + AggregationDestinationPrefix types.Bool `tfsdk:"aggregation_destination_prefix"` + AggregationProtocolPort types.Bool `tfsdk:"aggregation_protocol_port"` + AggregationSourceDestinationPrefix types.Bool `tfsdk:"aggregation_source_destination_prefix"` + AggregationSourceDestinationPrefixCaidaCompliant types.Bool `tfsdk:"aggregation_source_destination_prefix_caida_compliant"` + AggregationSourcePrefix types.Bool `tfsdk:"aggregation_source_prefix"` + LocalDump types.Bool `tfsdk:"local_dump"` + NoLocalDump types.Bool `tfsdk:"no_local_dump"` + Hostname types.String `tfsdk:"hostname"` + Port types.Int64 `tfsdk:"port"` + AutonomousSystemType types.String `tfsdk:"autonomous_system_type"` + Dscp types.Int64 `tfsdk:"dscp"` + ForwardingClass types.String `tfsdk:"forwarding_class"` + RoutingInstance types.String `tfsdk:"routing_instance"` + SourceAddress types.String `tfsdk:"source_address"` + Version types.Int64 `tfsdk:"version"` + Version9Template types.String `tfsdk:"version9_template"` + VersionIPFixTemplate types.String `tfsdk:"version_ipfix_template"` + } `tfsdk:"flow_server"` + Interface []forwardingoptionsSamplingInstanceBlockOutputBlockInterface `tfsdk:"interface"` + } `tfsdk:"family_inet_output"` + FamilyInet6Input []struct { + MaxPacketsPerSecond types.Int64 `tfsdk:"max_packets_per_second"` + MaximumPacketLength types.Int64 `tfsdk:"maximum_packet_length"` + Rate types.Int64 `tfsdk:"rate"` + RunLength types.Int64 `tfsdk:"run_length"` + } `tfsdk:"family_inet6_input"` + FamilyInet6Output []struct { + AggregateExportInterval types.Int64 `tfsdk:"aggregate_export_interval"` + ExtensionService []types.String `tfsdk:"extension_service"` + FlowActiveTimeout types.Int64 `tfsdk:"flow_active_timeout"` + FlowInactiveTimeout types.Int64 `tfsdk:"flow_inactive_timeout"` + InlineJflowExportRate types.Int64 `tfsdk:"inline_jflow_export_rate"` + InlineJflowSourceAddress types.String `tfsdk:"inline_jflow_source_address"` + FlowServer []struct { + AggregationAutonomousSystem types.Bool `tfsdk:"aggregation_autonomous_system"` + AggregationDestinationPrefix types.Bool `tfsdk:"aggregation_destination_prefix"` + AggregationProtocolPort types.Bool `tfsdk:"aggregation_protocol_port"` + AggregationSourceDestinationPrefix types.Bool `tfsdk:"aggregation_source_destination_prefix"` + AggregationSourceDestinationPrefixCaidaCompliant types.Bool `tfsdk:"aggregation_source_destination_prefix_caida_compliant"` + AggregationSourcePrefix types.Bool `tfsdk:"aggregation_source_prefix"` + LocalDump types.Bool `tfsdk:"local_dump"` + NoLocalDump types.Bool `tfsdk:"no_local_dump"` + Hostname types.String `tfsdk:"hostname"` + Port types.Int64 `tfsdk:"port"` + AutonomousSystemType types.String `tfsdk:"autonomous_system_type"` + Dscp types.Int64 `tfsdk:"dscp"` + ForwardingClass types.String `tfsdk:"forwarding_class"` + RoutingInstance types.String `tfsdk:"routing_instance"` + SourceAddress types.String `tfsdk:"source_address"` + Version9Template types.String `tfsdk:"version9_template"` + VersionIPFixTemplate types.String `tfsdk:"version_ipfix_template"` + } `tfsdk:"flow_server"` + Interface []forwardingoptionsSamplingInstanceBlockOutputBlockInterface `tfsdk:"interface"` + } `tfsdk:"family_inet6_output"` + FamilyMplsInput []struct { + MaxPacketsPerSecond types.Int64 `tfsdk:"max_packets_per_second"` + MaximumPacketLength types.Int64 `tfsdk:"maximum_packet_length"` + Rate types.Int64 `tfsdk:"rate"` + RunLength types.Int64 `tfsdk:"run_length"` + } `tfsdk:"family_mpls_input"` + FamilyMplsOutput []struct { + AggregateExportInterval types.Int64 `tfsdk:"aggregate_export_interval"` + FlowActiveTimeout types.Int64 `tfsdk:"flow_active_timeout"` + FlowInactiveTimeout types.Int64 `tfsdk:"flow_inactive_timeout"` + InlineJflowExportRate types.Int64 `tfsdk:"inline_jflow_export_rate"` + InlineJflowSourceAddress types.String `tfsdk:"inline_jflow_source_address"` + FlowServer []struct { + AggregationAutonomousSystem types.Bool `tfsdk:"aggregation_autonomous_system"` + AggregationDestinationPrefix types.Bool `tfsdk:"aggregation_destination_prefix"` + AggregationProtocolPort types.Bool `tfsdk:"aggregation_protocol_port"` + AggregationSourceDestinationPrefix types.Bool `tfsdk:"aggregation_source_destination_prefix"` + AggregationSourceDestinationPrefixCaidaCompliant types.Bool `tfsdk:"aggregation_source_destination_prefix_caida_compliant"` + AggregationSourcePrefix types.Bool `tfsdk:"aggregation_source_prefix"` + LocalDump types.Bool `tfsdk:"local_dump"` + NoLocalDump types.Bool `tfsdk:"no_local_dump"` + Hostname types.String `tfsdk:"hostname"` + Port types.Int64 `tfsdk:"port"` + AutonomousSystemType types.String `tfsdk:"autonomous_system_type"` + Dscp types.Int64 `tfsdk:"dscp"` + ForwardingClass types.String `tfsdk:"forwarding_class"` + RoutingInstance types.String `tfsdk:"routing_instance"` + SourceAddress types.String `tfsdk:"source_address"` + Version9Template types.String `tfsdk:"version9_template"` + VersionIPFixTemplate types.String `tfsdk:"version_ipfix_template"` + } `tfsdk:"flow_server"` + Interface []forwardingoptionsSamplingInstanceBlockOutputBlockInterface `tfsdk:"interface"` + } `tfsdk:"family_mpls_output"` + Input []struct { + MaxPacketsPerSecond types.Int64 `tfsdk:"max_packets_per_second"` + MaximumPacketLength types.Int64 `tfsdk:"maximum_packet_length"` + Rate types.Int64 `tfsdk:"rate"` + RunLength types.Int64 `tfsdk:"run_length"` + } `tfsdk:"input"` } var dataV0 modelV0 @@ -178,25 +498,138 @@ func upgradeForwardingoptionsSamplingInstanceStateV0toV1( dataV1.Name = dataV0.Name dataV1.Disable = dataV0.Disable if len(dataV0.FamilyInetInput) > 0 { - dataV1.FamilyInetInput = &dataV0.FamilyInetInput[0] + dataV1.FamilyInetInput = &forwardingoptionsSamplingInstanceBlockInput{ + MaxPacketsPerSecond: dataV0.FamilyInetInput[0].MaxPacketsPerSecond, + MaximumPacketLength: dataV0.FamilyInetInput[0].MaximumPacketLength, + Rate: dataV0.FamilyInetInput[0].Rate, + RunLength: dataV0.FamilyInetInput[0].RunLength, + } } if len(dataV0.FamilyInetOutput) > 0 { - dataV1.FamilyInetOutput = &dataV0.FamilyInetOutput[0] + dataV1.FamilyInetOutput = &forwardingoptionsSamplingInstanceBlockFamilyInetOutput{ + AggregateExportInterval: dataV0.FamilyInetOutput[0].AggregateExportInterval, + ExtensionService: dataV0.FamilyInetOutput[0].ExtensionService, + FlowActiveTimeout: dataV0.FamilyInetOutput[0].FlowActiveTimeout, + FlowInactiveTimeout: dataV0.FamilyInetOutput[0].FlowInactiveTimeout, + InlineJflowExportRate: dataV0.FamilyInetOutput[0].InlineJflowExportRate, + InlineJflowSourceAddress: dataV0.FamilyInetOutput[0].InlineJflowSourceAddress, + Interface: dataV0.FamilyInetOutput[0].Interface, + } + for _, blockV0 := range dataV0.FamilyInetOutput[0].FlowServer { + dataV1.FamilyInetOutput.FlowServer = append(dataV1.FamilyInetOutput.FlowServer, + forwardingoptionsSamplingInstanceBlockFamilyInetOutputBlockFlowServer{ + AggregationAutonomousSystem: blockV0.AggregationAutonomousSystem, + AggregationDestinationPrefix: blockV0.AggregationDestinationPrefix, + AggregationProtocolPort: blockV0.AggregationProtocolPort, + AggregationSourceDestinationPrefix: blockV0.AggregationSourceDestinationPrefix, + AggregationSourceDestinationPrefixCaidaCompliant: blockV0.AggregationSourceDestinationPrefixCaidaCompliant, + AggregationSourcePrefix: blockV0.AggregationSourcePrefix, + LocalDump: blockV0.LocalDump, + NoLocalDump: blockV0.NoLocalDump, + Hostname: blockV0.Hostname, + Port: blockV0.Port, + AutonomousSystemType: blockV0.AutonomousSystemType, + Dscp: blockV0.Dscp, + ForwardingClass: blockV0.ForwardingClass, + RoutingInstance: blockV0.RoutingInstance, + SourceAddress: blockV0.SourceAddress, + Version: blockV0.Version, + Version9Template: blockV0.Version9Template, + VersionIPFixTemplate: blockV0.VersionIPFixTemplate, + }, + ) + } } if len(dataV0.FamilyInet6Input) > 0 { - dataV1.FamilyInet6Input = &dataV0.FamilyInet6Input[0] + dataV1.FamilyInet6Input = &forwardingoptionsSamplingInstanceBlockInput{ + MaxPacketsPerSecond: dataV0.FamilyInet6Input[0].MaxPacketsPerSecond, + MaximumPacketLength: dataV0.FamilyInet6Input[0].MaximumPacketLength, + Rate: dataV0.FamilyInet6Input[0].Rate, + RunLength: dataV0.FamilyInet6Input[0].RunLength, + } } if len(dataV0.FamilyInet6Output) > 0 { - dataV1.FamilyInet6Output = &dataV0.FamilyInet6Output[0] + dataV1.FamilyInet6Output = &forwardingoptionsSamplingInstanceBlockFamilyInet6Output{ + AggregateExportInterval: dataV0.FamilyInet6Output[0].AggregateExportInterval, + ExtensionService: dataV0.FamilyInet6Output[0].ExtensionService, + FlowActiveTimeout: dataV0.FamilyInet6Output[0].FlowActiveTimeout, + FlowInactiveTimeout: dataV0.FamilyInet6Output[0].FlowInactiveTimeout, + InlineJflowExportRate: dataV0.FamilyInet6Output[0].InlineJflowExportRate, + InlineJflowSourceAddress: dataV0.FamilyInet6Output[0].InlineJflowSourceAddress, + Interface: dataV0.FamilyInet6Output[0].Interface, + } + for _, blockV0 := range dataV0.FamilyInet6Output[0].FlowServer { + dataV1.FamilyInet6Output.FlowServer = append(dataV1.FamilyInet6Output.FlowServer, + forwardingoptionsSamplingInstanceBlockOutputBlockFlowServer{ + AggregationAutonomousSystem: blockV0.AggregationAutonomousSystem, + AggregationDestinationPrefix: blockV0.AggregationDestinationPrefix, + AggregationProtocolPort: blockV0.AggregationProtocolPort, + AggregationSourceDestinationPrefix: blockV0.AggregationSourceDestinationPrefix, + AggregationSourceDestinationPrefixCaidaCompliant: blockV0.AggregationSourceDestinationPrefixCaidaCompliant, + AggregationSourcePrefix: blockV0.AggregationSourcePrefix, + LocalDump: blockV0.LocalDump, + NoLocalDump: blockV0.NoLocalDump, + Hostname: blockV0.Hostname, + Port: blockV0.Port, + AutonomousSystemType: blockV0.AutonomousSystemType, + Dscp: blockV0.Dscp, + ForwardingClass: blockV0.ForwardingClass, + RoutingInstance: blockV0.RoutingInstance, + SourceAddress: blockV0.SourceAddress, + Version9Template: blockV0.Version9Template, + VersionIPFixTemplate: blockV0.VersionIPFixTemplate, + }, + ) + } } if len(dataV0.FamilyMplsInput) > 0 { - dataV1.FamilyMplsInput = &dataV0.FamilyMplsInput[0] + dataV1.FamilyMplsInput = &forwardingoptionsSamplingInstanceBlockInput{ + MaxPacketsPerSecond: dataV0.FamilyMplsInput[0].MaxPacketsPerSecond, + MaximumPacketLength: dataV0.FamilyMplsInput[0].MaximumPacketLength, + Rate: dataV0.FamilyMplsInput[0].Rate, + RunLength: dataV0.FamilyMplsInput[0].RunLength, + } } if len(dataV0.FamilyMplsOutput) > 0 { - dataV1.FamilyMplsOutput = &dataV0.FamilyMplsOutput[0] + dataV1.FamilyMplsOutput = &forwardingoptionsSamplingInstanceBlockFamilyMplsOutput{ + AggregateExportInterval: dataV0.FamilyMplsOutput[0].AggregateExportInterval, + FlowActiveTimeout: dataV0.FamilyMplsOutput[0].FlowActiveTimeout, + FlowInactiveTimeout: dataV0.FamilyMplsOutput[0].FlowInactiveTimeout, + InlineJflowExportRate: dataV0.FamilyMplsOutput[0].InlineJflowExportRate, + InlineJflowSourceAddress: dataV0.FamilyMplsOutput[0].InlineJflowSourceAddress, + Interface: dataV0.FamilyMplsOutput[0].Interface, + } + for _, blockV0 := range dataV0.FamilyMplsOutput[0].FlowServer { + dataV1.FamilyMplsOutput.FlowServer = append(dataV1.FamilyMplsOutput.FlowServer, + forwardingoptionsSamplingInstanceBlockOutputBlockFlowServer{ + AggregationAutonomousSystem: blockV0.AggregationAutonomousSystem, + AggregationDestinationPrefix: blockV0.AggregationDestinationPrefix, + AggregationProtocolPort: blockV0.AggregationProtocolPort, + AggregationSourceDestinationPrefix: blockV0.AggregationSourceDestinationPrefix, + AggregationSourceDestinationPrefixCaidaCompliant: blockV0.AggregationSourceDestinationPrefixCaidaCompliant, + AggregationSourcePrefix: blockV0.AggregationSourcePrefix, + LocalDump: blockV0.LocalDump, + NoLocalDump: blockV0.NoLocalDump, + Hostname: blockV0.Hostname, + Port: blockV0.Port, + AutonomousSystemType: blockV0.AutonomousSystemType, + Dscp: blockV0.Dscp, + ForwardingClass: blockV0.ForwardingClass, + RoutingInstance: blockV0.RoutingInstance, + SourceAddress: blockV0.SourceAddress, + Version9Template: blockV0.Version9Template, + VersionIPFixTemplate: blockV0.VersionIPFixTemplate, + }, + ) + } } if len(dataV0.Input) > 0 { - dataV1.Input = &dataV0.Input[0] + dataV1.Input = &forwardingoptionsSamplingInstanceBlockInput{ + MaxPacketsPerSecond: dataV0.Input[0].MaxPacketsPerSecond, + MaximumPacketLength: dataV0.Input[0].MaximumPacketLength, + Rate: dataV0.Input[0].Rate, + RunLength: dataV0.Input[0].RunLength, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From 315f14428228e5c854e9d29980e0a77fc71e896d Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:33:26 +0200 Subject: [PATCH 25/48] r/security_ike_gateway: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_security_ike_gateway.go | 79 +++++++++++++------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/internal/providerfwk/upgradestate_security_ike_gateway.go b/internal/providerfwk/upgradestate_security_ike_gateway.go index 6cba7a81..7ecbc033 100644 --- a/internal/providerfwk/upgradestate_security_ike_gateway.go +++ b/internal/providerfwk/upgradestate_security_ike_gateway.go @@ -149,30 +149,43 @@ func (rsc *securityIkeGateway) UpgradeState(_ context.Context) map[int64]resourc func upgradeSecurityIkeGatewayV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { - //nolint:lll type modelV0 struct { - GeneralIkeID types.Bool `tfsdk:"general_ike_id"` - NoNatTraversal types.Bool `tfsdk:"no_nat_traversal"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ExternalInterface types.String `tfsdk:"external_interface"` - Policy types.String `tfsdk:"policy"` - Address []types.String `tfsdk:"address"` - LocalAddress types.String `tfsdk:"local_address"` - Version types.String `tfsdk:"version"` - Aaa []securityIkeGatewayBlockAaa `tfsdk:"aaa"` - DeadPeerDetection []securityIkeGatewayBlockDeadPeerDetection `tfsdk:"dead_peer_detection"` - DynamicRemote []struct { - ConnectionsLimit types.Int64 `tfsdk:"connections_limit"` - Hostname types.String `tfsdk:"hostname"` - IkeUserType types.String `tfsdk:"ike_user_type"` - Inet types.String `tfsdk:"inet"` - Inet6 types.String `tfsdk:"inet6"` - RejectDuplicateConnection types.Bool `tfsdk:"reject_duplicate_connection"` - UserAtHostname types.String `tfsdk:"user_at_hostname"` - DistinguishedName []securityIkeGatewayBlockDynamicRemoteBlockDistinguishedName `tfsdk:"distinguished_name"` + GeneralIkeID types.Bool `tfsdk:"general_ike_id"` + NoNatTraversal types.Bool `tfsdk:"no_nat_traversal"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ExternalInterface types.String `tfsdk:"external_interface"` + Policy types.String `tfsdk:"policy"` + Address []types.String `tfsdk:"address"` + LocalAddress types.String `tfsdk:"local_address"` + Version types.String `tfsdk:"version"` + Aaa []struct { + AccessProfile types.String `tfsdk:"access_profile"` + ClientPassword types.String `tfsdk:"client_password"` + ClientUsername types.String `tfsdk:"client_username"` + } `tfsdk:"aaa"` + DeadPeerDetection []struct { + Interval types.Int64 `tfsdk:"interval"` + SendMode types.String `tfsdk:"send_mode"` + Threshold types.Int64 `tfsdk:"threshold"` + } `tfsdk:"dead_peer_detection"` + DynamicRemote []struct { + ConnectionsLimit types.Int64 `tfsdk:"connections_limit"` + Hostname types.String `tfsdk:"hostname"` + IkeUserType types.String `tfsdk:"ike_user_type"` + Inet types.String `tfsdk:"inet"` + Inet6 types.String `tfsdk:"inet6"` + RejectDuplicateConnection types.Bool `tfsdk:"reject_duplicate_connection"` + UserAtHostname types.String `tfsdk:"user_at_hostname"` + DistinguishedName []struct { + Container types.String `tfsdk:"container"` + Wildcard types.String `tfsdk:"wildcard"` + } `tfsdk:"distinguished_name"` } `tfsdk:"dynamic_remote"` - LocalIdentity []securityIkeGatewayBlockLocalIdentity `tfsdk:"local_identity"` + LocalIdentity []struct { + Type types.String `tfsdk:"type"` + Value types.String `tfsdk:"value"` + } `tfsdk:"local_identity"` RemoteIdentity []struct { Type types.String `tfsdk:"type"` Value types.String `tfsdk:"value"` @@ -206,17 +219,31 @@ func upgradeSecurityIkeGatewayV0toV1( UserAtHostname: dataV0.DynamicRemote[0].UserAtHostname, } if len(dataV0.DynamicRemote[0].DistinguishedName) > 0 { - dataV1.DynamicRemote.DistinguishedName = &dataV0.DynamicRemote[0].DistinguishedName[0] + dataV1.DynamicRemote.DistinguishedName = &securityIkeGatewayBlockDynamicRemoteBlockDistinguishedName{ + Container: dataV0.DynamicRemote[0].DistinguishedName[0].Container, + Wildcard: dataV0.DynamicRemote[0].DistinguishedName[0].Wildcard, + } } } if len(dataV0.Aaa) > 0 { - dataV1.Aaa = &dataV0.Aaa[0] + dataV1.Aaa = &securityIkeGatewayBlockAaa{ + AccessProfile: dataV0.Aaa[0].AccessProfile, + ClientPassword: dataV0.Aaa[0].ClientPassword, + ClientUsername: dataV0.Aaa[0].ClientUsername, + } } if len(dataV0.DeadPeerDetection) > 0 { - dataV1.DeadPeerDetection = &dataV0.DeadPeerDetection[0] + dataV1.DeadPeerDetection = &securityIkeGatewayBlockDeadPeerDetection{ + Interval: dataV0.DeadPeerDetection[0].Interval, + SendMode: dataV0.DeadPeerDetection[0].SendMode, + Threshold: dataV0.DeadPeerDetection[0].Threshold, + } } if len(dataV0.LocalIdentity) > 0 { - dataV1.LocalIdentity = &dataV0.LocalIdentity[0] + dataV1.LocalIdentity = &securityIkeGatewayBlockLocalIdentity{ + Type: dataV0.LocalIdentity[0].Type, + Value: dataV0.LocalIdentity[0].Value, + } } if len(dataV0.RemoteIdentity) > 0 { dataV1.RemoteIdentity = &securityIkeGatewayBlockRemoteIdentity{ From 58e6cebbc95a2f4b6b4d922e81e0b777f24bda1f Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:34:03 +0200 Subject: [PATCH 26/48] r/security_ipsec_vpn: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_security_ipsec_vpn.go | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/internal/providerfwk/upgradestate_security_ipsec_vpn.go b/internal/providerfwk/upgradestate_security_ipsec_vpn.go index 8b690ccc..1d117db1 100644 --- a/internal/providerfwk/upgradestate_security_ipsec_vpn.go +++ b/internal/providerfwk/upgradestate_security_ipsec_vpn.go @@ -96,14 +96,29 @@ func upgradeSecurityIpsecVpnV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - BindInterface types.String `tfsdk:"bind_interface"` - DfBit types.String `tfsdk:"df_bit"` - EstablishTunnels types.String `tfsdk:"establish_tunnels"` - Ike []securityIpsecVpnBlockIke `tfsdk:"ike"` - TrafficSelector []securityIpsecVpnBlockTrafficSelector `tfsdk:"traffic_selector"` - VpnMonitor []securityIpsecVpnBlockVpnMonitor `tfsdk:"vpn_monitor"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + BindInterface types.String `tfsdk:"bind_interface"` + DfBit types.String `tfsdk:"df_bit"` + EstablishTunnels types.String `tfsdk:"establish_tunnels"` + Ike []struct { + Gateway types.String `tfsdk:"gateway"` + Policy types.String `tfsdk:"policy"` + IdentityLocal types.String `tfsdk:"identity_local"` + IdentityRemote types.String `tfsdk:"identity_remote"` + IdentityService types.String `tfsdk:"identity_service"` + } `tfsdk:"ike"` + TrafficSelector []struct { + Name types.String `tfsdk:"name"` + LocalIP types.String `tfsdk:"local_ip"` + RemoteIP types.String `tfsdk:"remote_ip"` + } `tfsdk:"traffic_selector"` + VpnMonitor []struct { + Optimized types.Bool `tfsdk:"optimized"` + SourceInterfaceAuto types.Bool `tfsdk:"source_interface_auto"` + DestinationIP types.String `tfsdk:"destination_ip"` + SourceInterface types.String `tfsdk:"source_interface"` + } `tfsdk:"vpn_monitor"` } var dataV0 modelV0 @@ -119,11 +134,30 @@ func upgradeSecurityIpsecVpnV0toV1( dataV1.DfBit = dataV0.DfBit dataV1.EstablishTunnels = dataV0.EstablishTunnels if len(dataV0.Ike) > 0 { - dataV1.Ike = &dataV0.Ike[0] + dataV1.Ike = &securityIpsecVpnBlockIke{ + Gateway: dataV0.Ike[0].Gateway, + Policy: dataV0.Ike[0].Policy, + IdentityLocal: dataV0.Ike[0].IdentityLocal, + IdentityRemote: dataV0.Ike[0].IdentityRemote, + IdentityService: dataV0.Ike[0].IdentityService, + } + } + for _, blockV0 := range dataV0.TrafficSelector { + dataV1.TrafficSelector = append(dataV1.TrafficSelector, + securityIpsecVpnBlockTrafficSelector{ + Name: blockV0.Name, + LocalIP: blockV0.LocalIP, + RemoteIP: blockV0.RemoteIP, + }, + ) } - dataV1.TrafficSelector = dataV0.TrafficSelector if len(dataV0.VpnMonitor) > 0 { - dataV1.VpnMonitor = &dataV0.VpnMonitor[0] + dataV1.VpnMonitor = &securityIpsecVpnBlockVpnMonitor{ + Optimized: dataV0.VpnMonitor[0].Optimized, + SourceInterfaceAuto: dataV0.VpnMonitor[0].SourceInterfaceAuto, + DestinationIP: dataV0.VpnMonitor[0].DestinationIP, + SourceInterface: dataV0.VpnMonitor[0].SourceInterface, + } if !dataV1.VpnMonitor.SourceInterfaceAuto.IsNull() && !dataV1.VpnMonitor.SourceInterfaceAuto.ValueBool() { dataV1.VpnMonitor.SourceInterfaceAuto = types.BoolNull() } From ccecfa683933f053bce78607d94a453240e3af07 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:34:37 +0200 Subject: [PATCH 27/48] r/security_nat_destination: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_security_nat_destination.go | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/internal/providerfwk/upgradestate_security_nat_destination.go b/internal/providerfwk/upgradestate_security_nat_destination.go index 1705140d..ad969c0a 100644 --- a/internal/providerfwk/upgradestate_security_nat_destination.go +++ b/internal/providerfwk/upgradestate_security_nat_destination.go @@ -97,20 +97,26 @@ func upgradeSecurityNatDestinationV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - From []securityNatDestinationBlockFrom `tfsdk:"from"` - Rule []struct { - Name types.String `tfsdk:"name"` - DestinationAddress types.String `tfsdk:"destination_address"` - DestinationAddressName types.String `tfsdk:"destination_address_name"` - Application []types.String `tfsdk:"application"` - DestinationPort []types.String `tfsdk:"destination_port"` - Protocol []types.String `tfsdk:"protocol"` - SourceAddress []types.String `tfsdk:"source_address"` - SourceAddressName []types.String `tfsdk:"source_address_name"` - Then []securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` + } `tfsdk:"from"` + Rule []struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + Application []types.String `tfsdk:"application"` + DestinationPort []types.String `tfsdk:"destination_port"` + Protocol []types.String `tfsdk:"protocol"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + Then []struct { + Type types.String `tfsdk:"type"` + Pool types.String `tfsdk:"pool"` + } `tfsdk:"then"` } `tfsdk:"rule"` } @@ -125,7 +131,10 @@ func upgradeSecurityNatDestinationV0toV1( dataV1.Name = dataV0.Name dataV1.Description = dataV0.Description if len(dataV0.From) > 0 { - dataV1.From = &dataV0.From[0] + dataV1.From = &securityNatDestinationBlockFrom{ + Type: dataV0.From[0].Type, + Value: dataV0.From[0].Value, + } } for _, blockV0 := range dataV0.Rule { blockV1 := securityNatDestinationBlockRule{ @@ -139,7 +148,10 @@ func upgradeSecurityNatDestinationV0toV1( SourceAddressName: blockV0.SourceAddressName, } if len(blockV0.Then) > 0 { - blockV1.Then = &blockV0.Then[0] + blockV1.Then = &securityNatDestinationBlockRuleBlockThen{ + Type: blockV0.Then[0].Type, + Pool: blockV0.Then[0].Pool, + } } dataV1.Rule = append(dataV1.Rule, blockV1) } From 4eedae535f3021e14fc9298c82ebd26c00e1fa60 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:35:03 +0200 Subject: [PATCH 28/48] r/security_nat_source: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../resource_security_nat_source.go | 2 +- .../upgradestate_security_nat_source.go | 62 +++++++++++++++---- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/internal/providerfwk/resource_security_nat_source.go b/internal/providerfwk/resource_security_nat_source.go index ce409e11..0ac2c6ac 100644 --- a/internal/providerfwk/resource_security_nat_source.go +++ b/internal/providerfwk/resource_security_nat_source.go @@ -357,7 +357,7 @@ type securityNatSourceBlockRule struct { type securityNatSourceBlockRuleConfig struct { Name types.String `tfsdk:"name"` Match *securityNatSourceBlockRuleBlockMatchConfig `tfsdk:"match"` - Then *securityNatDestinationBlockRuleBlockThen `tfsdk:"then"` + Then *securityNatSourceBlockRuleBlockThen `tfsdk:"then"` } type securityNatSourceBlockRuleBlockMatch struct { diff --git a/internal/providerfwk/upgradestate_security_nat_source.go b/internal/providerfwk/upgradestate_security_nat_source.go index f94810e6..8c62117f 100644 --- a/internal/providerfwk/upgradestate_security_nat_source.go +++ b/internal/providerfwk/upgradestate_security_nat_source.go @@ -122,15 +122,33 @@ func upgradeSecurityNatSourceV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - From []securityNatSourceBlockFromTo `tfsdk:"from"` - To []securityNatSourceBlockFromTo `tfsdk:"to"` - Rule []struct { - Name types.String `tfsdk:"name"` - Match []securityNatSourceBlockRuleBlockMatch `tfsdk:"match"` - Then []securityNatSourceBlockRuleBlockThen `tfsdk:"then"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` + } `tfsdk:"from"` + To []struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` + } `tfsdk:"to"` + Rule []struct { + Name types.String `tfsdk:"name"` + Match []struct { + Application []types.String `tfsdk:"application"` + DestinationAddress []types.String `tfsdk:"destination_address"` + DestinationAddressName []types.String `tfsdk:"destination_address_name"` + DestinationPort []types.String `tfsdk:"destination_port"` + Protocol []types.String `tfsdk:"protocol"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + } `tfsdk:"match"` + Then []struct { + Type types.String `tfsdk:"type"` + Pool types.String `tfsdk:"pool"` + } `tfsdk:"then"` } `tfsdk:"rule"` } @@ -145,20 +163,38 @@ func upgradeSecurityNatSourceV0toV1( dataV1.Name = dataV0.Name dataV1.Description = dataV0.Description if len(dataV0.From) > 0 { - dataV1.From = &dataV0.From[0] + dataV1.From = &securityNatSourceBlockFromTo{ + Type: dataV0.From[0].Type, + Value: dataV0.From[0].Value, + } } if len(dataV0.To) > 0 { - dataV1.To = &dataV0.To[0] + dataV1.To = &securityNatSourceBlockFromTo{ + Type: dataV0.To[0].Type, + Value: dataV0.To[0].Value, + } } for _, blockV0 := range dataV0.Rule { blockV1 := securityNatSourceBlockRule{ Name: blockV0.Name, } if len(blockV0.Match) > 0 { - blockV1.Match = &blockV0.Match[0] + blockV1.Match = &securityNatSourceBlockRuleBlockMatch{ + Application: blockV0.Match[0].Application, + DestinationAddress: blockV0.Match[0].DestinationAddress, + DestinationAddressName: blockV0.Match[0].DestinationAddressName, + DestinationPort: blockV0.Match[0].DestinationPort, + Protocol: blockV0.Match[0].Protocol, + SourceAddress: blockV0.Match[0].SourceAddress, + SourceAddressName: blockV0.Match[0].SourceAddressName, + SourcePort: blockV0.Match[0].SourcePort, + } } if len(blockV0.Then) > 0 { - blockV1.Then = &blockV0.Then[0] + blockV1.Then = &securityNatSourceBlockRuleBlockThen{ + Type: blockV0.Then[0].Type, + Pool: blockV0.Then[0].Pool, + } } dataV1.Rule = append(dataV1.Rule, blockV1) } From 3d06ae1d2cc4fad9c54e285bbfc01c8f22ecaf51 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:35:28 +0200 Subject: [PATCH 29/48] r/security_nat_static: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_security_nat_static.go | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/internal/providerfwk/upgradestate_security_nat_static.go b/internal/providerfwk/upgradestate_security_nat_static.go index a9b6d9e2..55a4d85d 100644 --- a/internal/providerfwk/upgradestate_security_nat_static.go +++ b/internal/providerfwk/upgradestate_security_nat_static.go @@ -107,21 +107,30 @@ func upgradeSecurityNatStaticV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - ConfigureRulesSingly types.Bool `tfsdk:"configure_rules_singly"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - From []securityNatStaticBlockFrom `tfsdk:"from"` - Rule []struct { - Name types.String `tfsdk:"name"` - DestinationAddress types.String `tfsdk:"destination_address"` - DestinationAddressName types.String `tfsdk:"destination_address_name"` - DestinationPort types.Int64 `tfsdk:"destination_port"` - DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` - SourceAddress []types.String `tfsdk:"source_address"` - SourceAddressName []types.String `tfsdk:"source_address_name"` - SourcePort []types.String `tfsdk:"source_port"` - Then []securityNatStaticRuleBlockThen `tfsdk:"then"` + ConfigureRulesSingly types.Bool `tfsdk:"configure_rules_singly"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + From []struct { + Type types.String `tfsdk:"type"` + Value []types.String `tfsdk:"value"` + } `tfsdk:"from"` + Rule []struct { + Name types.String `tfsdk:"name"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then []struct { + Type types.String `tfsdk:"type"` + MappedPort types.Int64 `tfsdk:"mapped_port"` + MappedPortTo types.Int64 `tfsdk:"mapped_port_to"` + Prefix types.String `tfsdk:"prefix"` + RoutingInstance types.String `tfsdk:"routing_instance"` + } `tfsdk:"then"` } `tfsdk:"rule"` } @@ -137,7 +146,10 @@ func upgradeSecurityNatStaticV0toV1( dataV1.ConfigureRulesSingly = dataV0.ConfigureRulesSingly dataV1.Description = dataV0.Description if len(dataV0.From) > 0 { - dataV1.From = &dataV0.From[0] + dataV1.From = &securityNatStaticBlockFrom{ + Type: dataV0.From[0].Type, + Value: dataV0.From[0].Value, + } } for _, blockV0 := range dataV0.Rule { blockV1 := securityNatStaticBlockRule{ @@ -151,7 +163,13 @@ func upgradeSecurityNatStaticV0toV1( SourcePort: blockV0.SourcePort, } if len(blockV0.Then) > 0 { - blockV1.Then = &blockV0.Then[0] + blockV1.Then = &securityNatStaticRuleBlockThen{ + Type: blockV0.Then[0].Type, + MappedPort: blockV0.Then[0].MappedPort, + MappedPortTo: blockV0.Then[0].MappedPortTo, + Prefix: blockV0.Then[0].Prefix, + RoutingInstance: blockV0.Then[0].RoutingInstance, + } } dataV1.Rule = append(dataV1.Rule, blockV1) } From 08b2d7a01f58acfe0cf79ed42233c2bf8f1ebacb Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:35:51 +0200 Subject: [PATCH 30/48] r/security_nat_static_rule: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_security_nat_static_rule.go | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/internal/providerfwk/upgradestate_security_nat_static_rule.go b/internal/providerfwk/upgradestate_security_nat_static_rule.go index 6d0bb82e..c6d7e5dd 100644 --- a/internal/providerfwk/upgradestate_security_nat_static_rule.go +++ b/internal/providerfwk/upgradestate_security_nat_static_rule.go @@ -80,17 +80,23 @@ func upgradeSecurityNatStaticRuleV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - RuleSet types.String `tfsdk:"rule_set"` - DestinationAddress types.String `tfsdk:"destination_address"` - DestinationAddressName types.String `tfsdk:"destination_address_name"` - DestinationPort types.Int64 `tfsdk:"destination_port"` - DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` - SourceAddress []types.String `tfsdk:"source_address"` - SourceAddressName []types.String `tfsdk:"source_address_name"` - SourcePort []types.String `tfsdk:"source_port"` - Then []securityNatStaticRuleBlockThen `tfsdk:"then"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + RuleSet types.String `tfsdk:"rule_set"` + DestinationAddress types.String `tfsdk:"destination_address"` + DestinationAddressName types.String `tfsdk:"destination_address_name"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + DestiantionPortTo types.Int64 `tfsdk:"destination_port_to"` + SourceAddress []types.String `tfsdk:"source_address"` + SourceAddressName []types.String `tfsdk:"source_address_name"` + SourcePort []types.String `tfsdk:"source_port"` + Then []struct { + Type types.String `tfsdk:"type"` + MappedPort types.Int64 `tfsdk:"mapped_port"` + MappedPortTo types.Int64 `tfsdk:"mapped_port_to"` + Prefix types.String `tfsdk:"prefix"` + RoutingInstance types.String `tfsdk:"routing_instance"` + } `tfsdk:"then"` } var dataV0 modelV0 @@ -111,7 +117,13 @@ func upgradeSecurityNatStaticRuleV0toV1( dataV1.SourceAddressName = dataV0.SourceAddressName dataV1.SourcePort = dataV0.SourcePort if len(dataV0.Then) > 0 { - dataV1.Then = &dataV0.Then[0] + dataV1.Then = &securityNatStaticRuleBlockThen{ + Type: dataV0.Then[0].Type, + MappedPort: dataV0.Then[0].MappedPort, + MappedPortTo: dataV0.Then[0].MappedPortTo, + Prefix: dataV0.Then[0].Prefix, + RoutingInstance: dataV0.Then[0].RoutingInstance, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From a7f86bb2faa1687671579e5ef3d0c766f3e5c448 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:36:46 +0200 Subject: [PATCH 31/48] r/services_flowmonitoring_vipfix_template: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- ...services_flowmonitoring_vipfix_template.go | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/internal/providerfwk/upgradestate_services_flowmonitoring_vipfix_template.go b/internal/providerfwk/upgradestate_services_flowmonitoring_vipfix_template.go index 48e023b6..edb8cc4c 100644 --- a/internal/providerfwk/upgradestate_services_flowmonitoring_vipfix_template.go +++ b/internal/providerfwk/upgradestate_services_flowmonitoring_vipfix_template.go @@ -83,24 +83,26 @@ func (rsc *servicesFlowMonitoringVIPFixTemplate) UpgradeState(_ context.Context) func upgradeServicesFlowMonitoringVIPFixTemplateStateV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { - //nolint:lll type modelV0 struct { - FlowKeyFlowDirection types.Bool `tfsdk:"flow_key_flow_direction"` - FlowKeyVlanID types.Bool `tfsdk:"flow_key_vlan_id"` - NexthopLearningEnable types.Bool `tfsdk:"nexthop_learning_enable"` - NexthopLearningDisable types.Bool `tfsdk:"nexthop_learning_disable"` - TunnelObservationIPv4 types.Bool `tfsdk:"tunnel_observation_ipv4"` - TunnelObservationIPv6 types.Bool `tfsdk:"tunnel_observation_ipv6"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Type types.String `tfsdk:"type"` - FlowActiveTimeout types.Int64 `tfsdk:"flow_active_timeout"` - FlowInactiveTimeout types.Int64 `tfsdk:"flow_inactive_timeout"` - IPTemplateExportExtension []types.String `tfsdk:"ip_template_export_extension"` - ObservationDomainID types.Int64 `tfsdk:"observation_domain_id"` - OptionTemplateID types.Int64 `tfsdk:"option_template_id"` - TemplateID types.Int64 `tfsdk:"template_id"` - OptionRefreshRate []servicesFlowMonitoringVIPFixTemplateBlockRefreshRate `tfsdk:"option_refresh_rate"` + FlowKeyFlowDirection types.Bool `tfsdk:"flow_key_flow_direction"` + FlowKeyVlanID types.Bool `tfsdk:"flow_key_vlan_id"` + NexthopLearningEnable types.Bool `tfsdk:"nexthop_learning_enable"` + NexthopLearningDisable types.Bool `tfsdk:"nexthop_learning_disable"` + TunnelObservationIPv4 types.Bool `tfsdk:"tunnel_observation_ipv4"` + TunnelObservationIPv6 types.Bool `tfsdk:"tunnel_observation_ipv6"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + FlowActiveTimeout types.Int64 `tfsdk:"flow_active_timeout"` + FlowInactiveTimeout types.Int64 `tfsdk:"flow_inactive_timeout"` + IPTemplateExportExtension []types.String `tfsdk:"ip_template_export_extension"` + ObservationDomainID types.Int64 `tfsdk:"observation_domain_id"` + OptionTemplateID types.Int64 `tfsdk:"option_template_id"` + TemplateID types.Int64 `tfsdk:"template_id"` + OptionRefreshRate []struct { + Packets types.Int64 `tfsdk:"packets"` + Seconds types.Int64 `tfsdk:"seconds"` + } `tfsdk:"option_refresh_rate"` } var dataV0 modelV0 @@ -126,7 +128,10 @@ func upgradeServicesFlowMonitoringVIPFixTemplateStateV0toV1( dataV1.TunnelObservationIPv4 = dataV0.TunnelObservationIPv4 dataV1.TunnelObservationIPv6 = dataV0.TunnelObservationIPv6 if len(dataV0.OptionRefreshRate) > 0 { - dataV1.OptionRefreshRate = &dataV0.OptionRefreshRate[0] + dataV1.OptionRefreshRate = &servicesFlowMonitoringVIPFixTemplateBlockRefreshRate{ + Packets: dataV0.OptionRefreshRate[0].Packets, + Seconds: dataV0.OptionRefreshRate[0].Seconds, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From d1f4b85ba0bef64e2ec3f0e3881567c3732c6f80 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 22:58:29 +0200 Subject: [PATCH 32/48] r/security: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- internal/providerfwk/upgradestate_security.go | 316 ++++++++++++++---- 1 file changed, 243 insertions(+), 73 deletions(-) diff --git a/internal/providerfwk/upgradestate_security.go b/internal/providerfwk/upgradestate_security.go index 0c97b076..5aed9dbd 100644 --- a/internal/providerfwk/upgradestate_security.go +++ b/internal/providerfwk/upgradestate_security.go @@ -560,25 +560,48 @@ func upgradeSecurityV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - CleanOnDestroy types.Bool `tfsdk:"clean_on_destroy"` - ID types.String `tfsdk:"id"` - Alg []securityBlockAlg `tfsdk:"alg"` - Flow []struct { - AllowDNSReply types.Bool `tfsdk:"allow_dns_reply"` - AllowEmbeddedIcmp types.Bool `tfsdk:"allow_embedded_icmp"` - AllowReverseEcmp types.Bool `tfsdk:"allow_reverse_ecmp"` - EnableRerouteUniformLinkCheckNat types.Bool `tfsdk:"enable_reroute_uniform_link_check_nat"` - ForceIPReassembly types.Bool `tfsdk:"force_ip_reassembly"` - IpsecPerformanceAcceleration types.Bool `tfsdk:"ipsec_performance_acceleration"` - McastBufferEnhance types.Bool `tfsdk:"mcast_buffer_enhance"` - PreserveIncomingFragmentSize types.Bool `tfsdk:"preserve_incoming_fragment_size"` - SyncIcmpSession types.Bool `tfsdk:"sync_icmp_session"` - PendingSessQueueLength types.String `tfsdk:"pending_sess_queue_length"` - RouteChangeTimeout types.Int64 `tfsdk:"route_change_timeout"` - SynFloodProtectionMode types.String `tfsdk:"syn_flood_protection_mode"` - AdvancedOptions []securityBlockFlowBlockAdvancedOptions `tfsdk:"advanced_options"` - Aging []securityBlockFlowBlockAging `tfsdk:"aging"` - EthernetSwitching []struct { + CleanOnDestroy types.Bool `tfsdk:"clean_on_destroy"` + ID types.String `tfsdk:"id"` + Alg []struct { + DNSDisable types.Bool `tfsdk:"dns_disable"` + FtpDisable types.Bool `tfsdk:"ftp_disable"` + H323Disable types.Bool `tfsdk:"h323_disable"` + MgcpDisable types.Bool `tfsdk:"mgcp_disable"` + MsrpcDisable types.Bool `tfsdk:"msrpc_disable"` + PptpDisable types.Bool `tfsdk:"pptp_disable"` + RshDisable types.Bool `tfsdk:"rsh_disable"` + RtspDisable types.Bool `tfsdk:"rtsp_disable"` + SccpDisable types.Bool `tfsdk:"sccp_disable"` + SIPDisable types.Bool `tfsdk:"sip_disable"` + SQLDisable types.Bool `tfsdk:"sql_disable"` + SunrpcDisable types.Bool `tfsdk:"sunrpc_disable"` + TalkDisable types.Bool `tfsdk:"talk_disable"` + TftpDisable types.Bool `tfsdk:"tftp_disable"` + } `tfsdk:"alg"` + Flow []struct { + AllowDNSReply types.Bool `tfsdk:"allow_dns_reply"` + AllowEmbeddedIcmp types.Bool `tfsdk:"allow_embedded_icmp"` + AllowReverseEcmp types.Bool `tfsdk:"allow_reverse_ecmp"` + EnableRerouteUniformLinkCheckNat types.Bool `tfsdk:"enable_reroute_uniform_link_check_nat"` + ForceIPReassembly types.Bool `tfsdk:"force_ip_reassembly"` + IpsecPerformanceAcceleration types.Bool `tfsdk:"ipsec_performance_acceleration"` + McastBufferEnhance types.Bool `tfsdk:"mcast_buffer_enhance"` + PreserveIncomingFragmentSize types.Bool `tfsdk:"preserve_incoming_fragment_size"` + SyncIcmpSession types.Bool `tfsdk:"sync_icmp_session"` + PendingSessQueueLength types.String `tfsdk:"pending_sess_queue_length"` + RouteChangeTimeout types.Int64 `tfsdk:"route_change_timeout"` + SynFloodProtectionMode types.String `tfsdk:"syn_flood_protection_mode"` + AdvancedOptions []struct { + DropMatchingLinkLocalAddress types.Bool `tfsdk:"drop_matching_link_local_address"` + DropMatchingReservedIPAddress types.Bool `tfsdk:"drop_matching_reserved_ip_address"` + ReverseRoutePacketModeVR types.Bool `tfsdk:"reverse_route_packet_mode_vr"` + } `tfsdk:"advanced_options"` + Aging []struct { + EarlyAgeout types.Int64 `tfsdk:"early_ageout"` + HighWatermark types.Int64 `tfsdk:"high_watermark"` + LowWatermark types.Int64 `tfsdk:"low_watermark"` + } `tfsdk:"aging"` + EthernetSwitching []struct { BlockNonIPAll types.Bool `tfsdk:"block_non_ip_all"` BypassNonIPUnicast types.Bool `tfsdk:"bypass_non_ip_unicast"` BpduVlanFlooding types.Bool `tfsdk:"bpdu_vlan_flooding"` @@ -599,53 +622,115 @@ func upgradeSecurityV0toV1( } `tfsdk:"ipsec_vpn"` } `tfsdk:"tcp_mss"` TCPSession []struct { - FinInvalidateSession types.Bool `tfsdk:"fin_invalidate_session"` - NoSequenceCheck types.Bool `tfsdk:"no_sequence_check"` - NoSynCheck types.Bool `tfsdk:"no_syn_check"` - NoSynCheckInTunnel types.Bool `tfsdk:"no_syn_check_in_tunnel"` - RstInvalidateSession types.Bool `tfsdk:"rst_invalidate_session"` - RstSequenceCheck types.Bool `tfsdk:"rst_sequence_check"` - StrictSynCheck types.Bool `tfsdk:"strict_syn_check"` - MaximumWindow types.String `tfsdk:"maximum_window"` - TCPInitialTimeout types.Int64 `tfsdk:"tcp_initial_timeout"` - TimeWaitState []securityBlockFlowBlockTCPSessionBlockTimeWaitState `tfsdk:"time_wait_state"` + FinInvalidateSession types.Bool `tfsdk:"fin_invalidate_session"` + NoSequenceCheck types.Bool `tfsdk:"no_sequence_check"` + NoSynCheck types.Bool `tfsdk:"no_syn_check"` + NoSynCheckInTunnel types.Bool `tfsdk:"no_syn_check_in_tunnel"` + RstInvalidateSession types.Bool `tfsdk:"rst_invalidate_session"` + RstSequenceCheck types.Bool `tfsdk:"rst_sequence_check"` + StrictSynCheck types.Bool `tfsdk:"strict_syn_check"` + MaximumWindow types.String `tfsdk:"maximum_window"` + TCPInitialTimeout types.Int64 `tfsdk:"tcp_initial_timeout"` + TimeWaitState []struct { + ApplyToHalfCloseState types.Bool `tfsdk:"apply_to_half_close_state"` + SessionAgeout types.Bool `tfsdk:"session_ageout"` + SessionTimeout types.Int64 `tfsdk:"session_timeout"` + } `tfsdk:"time_wait_state"` } `tfsdk:"tcp_session"` } `tfsdk:"flow"` - ForwardingOptions []securityBlockForwardingOptions `tfsdk:"forwarding_options"` - ForwardingProcess []securityBlockForwardingProcess `tfsdk:"forwarding_process"` - IdpSecurityPackage []securityBlockIdpSecurityPackage `tfsdk:"idp_security_package"` + ForwardingOptions []struct { + Inet6Mode types.String `tfsdk:"inet6_mode"` + IsoModePacketBased types.Bool `tfsdk:"iso_mode_packet_based"` + MplsMode types.String `tfsdk:"mpls_mode"` + } `tfsdk:"forwarding_options"` + ForwardingProcess []struct { + EnhancedServicesMode types.Bool `tfsdk:"enhanced_services_mode"` + } `tfsdk:"forwarding_process"` + IdpSecurityPackage []struct { + AutomaticEnable types.Bool `tfsdk:"automatic_enable"` + InstallIgnoreVersionCheck types.Bool `tfsdk:"install_ignore_version_check"` + AutomaticInterval types.Int64 `tfsdk:"automatic_interval"` + AutomaticStartTime types.String `tfsdk:"automatic_start_time"` + ProxyProfile types.String `tfsdk:"proxy_profile"` + SourceAddress types.String `tfsdk:"source_address"` + URL types.String `tfsdk:"url"` + } `tfsdk:"idp_security_package"` IdpSensorConfiguration []struct { - LogCacheSize types.Int64 `tfsdk:"log_cache_size"` - SecurityConfigurationProtectionMode types.String `tfsdk:"security_configuration_protection_mode"` - LogSuppression []securityBlockIdpSensorConfigurationBlockLogSuppression `tfsdk:"log_suppression"` - PacketLog []securityBlockIdpSensorConfigurationBlockPacketLog `tfsdk:"packet_log"` + LogCacheSize types.Int64 `tfsdk:"log_cache_size"` + SecurityConfigurationProtectionMode types.String `tfsdk:"security_configuration_protection_mode"` + LogSuppression []struct { + Disable types.Bool `tfsdk:"disable"` + IncludeDestinationAddress types.Bool `tfsdk:"include_destination_address"` + NoIncludeDestinationAddress types.Bool `tfsdk:"no_include_destination_address"` + MaxLogsOperate types.Int64 `tfsdk:"max_logs_operate"` + MaxTimeReport types.Int64 `tfsdk:"max_time_report"` + StartLog types.Int64 `tfsdk:"start_log"` + } `tfsdk:"log_suppression"` + PacketLog []struct { + SourceAddress types.String `tfsdk:"source_address"` + HostAddress types.String `tfsdk:"host_address"` + HostPort types.Int64 `tfsdk:"host_port"` + MaxSessions types.Int64 `tfsdk:"max_sessions"` + ThresholdLoggingInterval types.Int64 `tfsdk:"threshold_logging_interval"` + TotalMemory types.Int64 `tfsdk:"total_memory"` + } `tfsdk:"packet_log"` } `tfsdk:"idp_sensor_configuration"` IkeTraceoptions []struct { - Flag []types.String `tfsdk:"flag"` - NoRemoteTrace types.Bool `tfsdk:"no_remote_trace"` - RateLimit types.Int64 `tfsdk:"rate_limit"` - File []securityBlockIkeTraceoptionsBlockFile `tfsdk:"file"` + Flag []types.String `tfsdk:"flag"` + NoRemoteTrace types.Bool `tfsdk:"no_remote_trace"` + RateLimit types.Int64 `tfsdk:"rate_limit"` + File []struct { + NoWorldReadable types.Bool `tfsdk:"no_world_readable"` + WorldReadable types.Bool `tfsdk:"world_readable"` + Name types.String `tfsdk:"name"` + Files types.Int64 `tfsdk:"files"` + Match types.String `tfsdk:"match"` + Size types.Int64 `tfsdk:"size"` + } `tfsdk:"file"` } `tfsdk:"ike_traceoptions"` Log []struct { - Disable types.Bool `tfsdk:"disable"` - Report types.Bool `tfsdk:"report"` - UtcTimestamp types.Bool `tfsdk:"utc_timestamp"` - EventRate types.Int64 `tfsdk:"event_rate"` - FacilityOverride types.String `tfsdk:"facility_override"` - Format types.String `tfsdk:"format"` - MaxDatabaseRecord types.Int64 `tfsdk:"max_database_record"` - Mode types.String `tfsdk:"mode"` - RateCap types.Int64 `tfsdk:"rate_cap"` - SourceAddress types.String `tfsdk:"source_address"` - SourceInterface types.String `tfsdk:"source_interface"` - File []securityBlockLogBlockFile `tfsdk:"file"` - Transport []securityBlockLogBlockTransport `tfsdk:"transport"` + Disable types.Bool `tfsdk:"disable"` + Report types.Bool `tfsdk:"report"` + UtcTimestamp types.Bool `tfsdk:"utc_timestamp"` + EventRate types.Int64 `tfsdk:"event_rate"` + FacilityOverride types.String `tfsdk:"facility_override"` + Format types.String `tfsdk:"format"` + MaxDatabaseRecord types.Int64 `tfsdk:"max_database_record"` + Mode types.String `tfsdk:"mode"` + RateCap types.Int64 `tfsdk:"rate_cap"` + SourceAddress types.String `tfsdk:"source_address"` + SourceInterface types.String `tfsdk:"source_interface"` + File []struct { + Files types.Int64 `tfsdk:"files"` + Name types.String `tfsdk:"name"` + Path types.String `tfsdk:"path"` + Size types.Int64 `tfsdk:"size"` + } `tfsdk:"file"` + Transport []struct { + Protocol types.String `tfsdk:"protocol"` + TCPConnections types.Int64 `tfsdk:"tcp_connections"` + TLSProfile types.String `tfsdk:"tls_profile"` + } `tfsdk:"transport"` } `tfsdk:"log"` - Policies []securityBlockPolicies `tfsdk:"policies"` - UserIdentificationAuthSource []securityBlockUserIdentificationAuthSource `tfsdk:"user_identification_auth_source"` - Utm []struct { - FeatureProfileWebFilteringType types.String `tfsdk:"feature_profile_web_filtering_type"` - FeatureProfileWebFilteringJuniperEnhancedServer []securityBlockUtmBlockFeatureProfileWebFilteringJuniperEnhancedServer `tfsdk:"feature_profile_web_filtering_juniper_enhanced_server"` + Policies []struct { + PolicyRematch types.Bool `tfsdk:"policy_rematch"` + PolicyRematchExtensive types.Bool `tfsdk:"policy_rematch_extensive"` + } `tfsdk:"policies"` + UserIdentificationAuthSource []struct { + ADAuthPriority types.Int64 `tfsdk:"ad_auth_priority"` + ArubaClearpassPriority types.Int64 `tfsdk:"aruba_clearpass_priority"` + FirewallAuthPriority types.Int64 `tfsdk:"firewall_auth_priority"` + LocalAuthPriority types.Int64 `tfsdk:"local_auth_priority"` + UnifiedAccessControlPriority types.Int64 `tfsdk:"unified_access_control_priority"` + } `tfsdk:"user_identification_auth_source"` + Utm []struct { + FeatureProfileWebFilteringType types.String `tfsdk:"feature_profile_web_filtering_type"` + FeatureProfileWebFilteringJuniperEnhancedServer []struct { + Host types.String `tfsdk:"host"` + Port types.Int64 `tfsdk:"port"` + ProxyProfile types.String `tfsdk:"proxy_profile"` + RoutingInstance types.String `tfsdk:"routing_instance"` + } `tfsdk:"feature_profile_web_filtering_juniper_enhanced_server"` } `tfsdk:"utm"` } @@ -662,7 +747,22 @@ func upgradeSecurityV0toV1( dataV1.CleanOnDestroy = types.BoolNull() } if len(dataV0.Alg) > 0 { - dataV1.Alg = &dataV0.Alg[0] + dataV1.Alg = &securityBlockAlg{ + DNSDisable: dataV0.Alg[0].DNSDisable, + FtpDisable: dataV0.Alg[0].FtpDisable, + H323Disable: dataV0.Alg[0].H323Disable, + MgcpDisable: dataV0.Alg[0].MgcpDisable, + MsrpcDisable: dataV0.Alg[0].MsrpcDisable, + PptpDisable: dataV0.Alg[0].PptpDisable, + RshDisable: dataV0.Alg[0].RshDisable, + RtspDisable: dataV0.Alg[0].RtspDisable, + SccpDisable: dataV0.Alg[0].SccpDisable, + SIPDisable: dataV0.Alg[0].SIPDisable, + SQLDisable: dataV0.Alg[0].SQLDisable, + SunrpcDisable: dataV0.Alg[0].SunrpcDisable, + TalkDisable: dataV0.Alg[0].TalkDisable, + TftpDisable: dataV0.Alg[0].TftpDisable, + } } if len(dataV0.Flow) > 0 { dataV1.Flow = &securityBlockFlow{ @@ -680,10 +780,18 @@ func upgradeSecurityV0toV1( SynFloodProtectionMode: dataV0.Flow[0].SynFloodProtectionMode, } if len(dataV0.Flow[0].AdvancedOptions) > 0 { - dataV1.Flow.AdvancedOptions = &dataV0.Flow[0].AdvancedOptions[0] + dataV1.Flow.AdvancedOptions = &securityBlockFlowBlockAdvancedOptions{ + DropMatchingLinkLocalAddress: dataV0.Flow[0].AdvancedOptions[0].DropMatchingLinkLocalAddress, + DropMatchingReservedIPAddress: dataV0.Flow[0].AdvancedOptions[0].DropMatchingReservedIPAddress, + ReverseRoutePacketModeVR: dataV0.Flow[0].AdvancedOptions[0].ReverseRoutePacketModeVR, + } } if len(dataV0.Flow[0].Aging) > 0 { - dataV1.Flow.Aging = &dataV0.Flow[0].Aging[0] + dataV1.Flow.Aging = &securityBlockFlowBlockAging{ + EarlyAgeout: dataV0.Flow[0].Aging[0].EarlyAgeout, + HighWatermark: dataV0.Flow[0].Aging[0].HighWatermark, + LowWatermark: dataV0.Flow[0].Aging[0].LowWatermark, + } } if len(dataV0.Flow[0].EthernetSwitching) > 0 { dataV1.Flow.EthernetSwitching = &securityBlockFlowBlockEthernetSwitching{ @@ -722,18 +830,36 @@ func upgradeSecurityV0toV1( TCPInitialTimeout: dataV0.Flow[0].TCPSession[0].TCPInitialTimeout, } if len(dataV0.Flow[0].TCPSession[0].TimeWaitState) > 0 { - dataV1.Flow.TCPSession.TimeWaitState = &dataV0.Flow[0].TCPSession[0].TimeWaitState[0] + dataV1.Flow.TCPSession.TimeWaitState = &securityBlockFlowBlockTCPSessionBlockTimeWaitState{ + ApplyToHalfCloseState: dataV0.Flow[0].TCPSession[0].TimeWaitState[0].ApplyToHalfCloseState, + SessionAgeout: dataV0.Flow[0].TCPSession[0].TimeWaitState[0].SessionAgeout, + SessionTimeout: dataV0.Flow[0].TCPSession[0].TimeWaitState[0].SessionTimeout, + } } } } if len(dataV0.ForwardingOptions) > 0 { - dataV1.ForwardingOptions = &dataV0.ForwardingOptions[0] + dataV1.ForwardingOptions = &securityBlockForwardingOptions{ + Inet6Mode: dataV0.ForwardingOptions[0].Inet6Mode, + IsoModePacketBased: dataV0.ForwardingOptions[0].IsoModePacketBased, + MplsMode: dataV0.ForwardingOptions[0].MplsMode, + } } if len(dataV0.ForwardingProcess) > 0 { - dataV1.ForwardingProcess = &dataV0.ForwardingProcess[0] + dataV1.ForwardingProcess = &securityBlockForwardingProcess{ + EnhancedServicesMode: dataV0.ForwardingProcess[0].EnhancedServicesMode, + } } if len(dataV0.IdpSecurityPackage) > 0 { - dataV1.IdpSecurityPackage = &dataV0.IdpSecurityPackage[0] + dataV1.IdpSecurityPackage = &securityBlockIdpSecurityPackage{ + AutomaticEnable: dataV0.IdpSecurityPackage[0].AutomaticEnable, + InstallIgnoreVersionCheck: dataV0.IdpSecurityPackage[0].InstallIgnoreVersionCheck, + AutomaticInterval: dataV0.IdpSecurityPackage[0].AutomaticInterval, + AutomaticStartTime: dataV0.IdpSecurityPackage[0].AutomaticStartTime, + ProxyProfile: dataV0.IdpSecurityPackage[0].ProxyProfile, + SourceAddress: dataV0.IdpSecurityPackage[0].SourceAddress, + URL: dataV0.IdpSecurityPackage[0].URL, + } } if len(dataV0.IdpSensorConfiguration) > 0 { dataV1.IdpSensorConfiguration = &securityBlockIdpSensorConfiguration{ @@ -741,10 +867,24 @@ func upgradeSecurityV0toV1( SecurityConfigurationProtectionMode: dataV0.IdpSensorConfiguration[0].SecurityConfigurationProtectionMode, } if len(dataV0.IdpSensorConfiguration[0].LogSuppression) > 0 { - dataV1.IdpSensorConfiguration.LogSuppression = &dataV0.IdpSensorConfiguration[0].LogSuppression[0] + dataV1.IdpSensorConfiguration.LogSuppression = &securityBlockIdpSensorConfigurationBlockLogSuppression{ + Disable: dataV0.IdpSensorConfiguration[0].LogSuppression[0].Disable, + IncludeDestinationAddress: dataV0.IdpSensorConfiguration[0].LogSuppression[0].IncludeDestinationAddress, + NoIncludeDestinationAddress: dataV0.IdpSensorConfiguration[0].LogSuppression[0].NoIncludeDestinationAddress, + MaxLogsOperate: dataV0.IdpSensorConfiguration[0].LogSuppression[0].MaxLogsOperate, + MaxTimeReport: dataV0.IdpSensorConfiguration[0].LogSuppression[0].MaxTimeReport, + StartLog: dataV0.IdpSensorConfiguration[0].LogSuppression[0].StartLog, + } } if len(dataV0.IdpSensorConfiguration[0].PacketLog) > 0 { - dataV1.IdpSensorConfiguration.PacketLog = &dataV0.IdpSensorConfiguration[0].PacketLog[0] + dataV1.IdpSensorConfiguration.PacketLog = &securityBlockIdpSensorConfigurationBlockPacketLog{ + SourceAddress: dataV0.IdpSensorConfiguration[0].PacketLog[0].SourceAddress, + HostAddress: dataV0.IdpSensorConfiguration[0].PacketLog[0].HostAddress, + HostPort: dataV0.IdpSensorConfiguration[0].PacketLog[0].HostPort, + MaxSessions: dataV0.IdpSensorConfiguration[0].PacketLog[0].MaxSessions, + ThresholdLoggingInterval: dataV0.IdpSensorConfiguration[0].PacketLog[0].ThresholdLoggingInterval, + TotalMemory: dataV0.IdpSensorConfiguration[0].PacketLog[0].TotalMemory, + } } } if len(dataV0.IkeTraceoptions) > 0 { @@ -754,7 +894,14 @@ func upgradeSecurityV0toV1( RateLimit: dataV0.IkeTraceoptions[0].RateLimit, } if len(dataV0.IkeTraceoptions[0].File) > 0 { - dataV1.IkeTraceoptions.File = &dataV0.IkeTraceoptions[0].File[0] + dataV1.IkeTraceoptions.File = &securityBlockIkeTraceoptionsBlockFile{ + NoWorldReadable: dataV0.IkeTraceoptions[0].File[0].NoWorldReadable, + WorldReadable: dataV0.IkeTraceoptions[0].File[0].WorldReadable, + Name: dataV0.IkeTraceoptions[0].File[0].Name, + Files: dataV0.IkeTraceoptions[0].File[0].Files, + Match: dataV0.IkeTraceoptions[0].File[0].Match, + Size: dataV0.IkeTraceoptions[0].File[0].Size, + } } } if len(dataV0.Log) > 0 { @@ -772,24 +919,47 @@ func upgradeSecurityV0toV1( SourceInterface: dataV0.Log[0].SourceInterface, } if len(dataV0.Log[0].File) > 0 { - dataV1.Log.File = &dataV0.Log[0].File[0] + dataV1.Log.File = &securityBlockLogBlockFile{ + Files: dataV0.Log[0].File[0].Files, + Name: dataV0.Log[0].File[0].Name, + Path: dataV0.Log[0].File[0].Path, + Size: dataV0.Log[0].File[0].Size, + } } if len(dataV0.Log[0].Transport) > 0 { - dataV1.Log.Transport = &dataV0.Log[0].Transport[0] + dataV1.Log.Transport = &securityBlockLogBlockTransport{ + Protocol: dataV0.Log[0].Transport[0].Protocol, + TCPConnections: dataV0.Log[0].Transport[0].TCPConnections, + TLSProfile: dataV0.Log[0].Transport[0].TLSProfile, + } } } if len(dataV0.Policies) > 0 { - dataV1.Policies = &dataV0.Policies[0] + dataV1.Policies = &securityBlockPolicies{ + PolicyRematch: dataV0.Policies[0].PolicyRematch, + PolicyRematchExtensive: dataV0.Policies[0].PolicyRematchExtensive, + } } if len(dataV0.UserIdentificationAuthSource) > 0 { - dataV1.UserIdentificationAuthSource = &dataV0.UserIdentificationAuthSource[0] + dataV1.UserIdentificationAuthSource = &securityBlockUserIdentificationAuthSource{ + ADAuthPriority: dataV0.UserIdentificationAuthSource[0].ADAuthPriority, + ArubaClearpassPriority: dataV0.UserIdentificationAuthSource[0].ArubaClearpassPriority, + FirewallAuthPriority: dataV0.UserIdentificationAuthSource[0].FirewallAuthPriority, + LocalAuthPriority: dataV0.UserIdentificationAuthSource[0].LocalAuthPriority, + UnifiedAccessControlPriority: dataV0.UserIdentificationAuthSource[0].UnifiedAccessControlPriority, + } } if len(dataV0.Utm) > 0 { dataV1.Utm = &securityBlockUtm{ FeatureProfileWebFilteringType: dataV0.Utm[0].FeatureProfileWebFilteringType, } if len(dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer) > 0 { - dataV1.Utm.FeatureProfileWebFilteringJuniperEnhancedServer = &dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer[0] + dataV1.Utm.FeatureProfileWebFilteringJuniperEnhancedServer = &securityBlockUtmBlockFeatureProfileWebFilteringJuniperEnhancedServer{ + Host: dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer[0].Host, + Port: dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer[0].Port, + ProxyProfile: dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer[0].ProxyProfile, + RoutingInstance: dataV0.Utm[0].FeatureProfileWebFilteringJuniperEnhancedServer[0].RoutingInstance, + } } } From b7ece7ac1b9d43076695ff79f116eda3edfc802d Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 28 Aug 2023 23:19:16 +0200 Subject: [PATCH 33/48] r/policyoptions_policy_statement: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- ...adestate_policyoptions_policy_statement.go | 174 ++++++++++++++---- 1 file changed, 140 insertions(+), 34 deletions(-) diff --git a/internal/providerfwk/upgradestate_policyoptions_policy_statement.go b/internal/providerfwk/upgradestate_policyoptions_policy_statement.go index 36318933..038b2244 100644 --- a/internal/providerfwk/upgradestate_policyoptions_policy_statement.go +++ b/internal/providerfwk/upgradestate_policyoptions_policy_statement.go @@ -37,7 +37,32 @@ func (rsc *policyoptionsPolicyStatement) UpgradeState(_ context.Context) map[int }, "then": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaThenAttributes(), + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Optional: true, + }, + "as_path_expand": schema.StringAttribute{ + Optional: true, + }, + "as_path_prepend": schema.StringAttribute{ + Optional: true, + }, + "default_action": schema.StringAttribute{ + Optional: true, + }, + "load_balance": schema.StringAttribute{ + Optional: true, + }, + "next": schema.StringAttribute{ + Optional: true, + }, + "next_hop": schema.StringAttribute{ + Optional: true, + }, + "origin": schema.StringAttribute{ + Optional: true, + }, + }, Blocks: map[string]schema.Block{ "community": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ @@ -111,7 +136,32 @@ func (rsc *policyoptionsPolicyStatement) UpgradeState(_ context.Context) map[int }, "then": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ - Attributes: rsc.schemaThenAttributes(), + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Optional: true, + }, + "as_path_expand": schema.StringAttribute{ + Optional: true, + }, + "as_path_prepend": schema.StringAttribute{ + Optional: true, + }, + "default_action": schema.StringAttribute{ + Optional: true, + }, + "load_balance": schema.StringAttribute{ + Optional: true, + }, + "next": schema.StringAttribute{ + Optional: true, + }, + "next_hop": schema.StringAttribute{ + Optional: true, + }, + "origin": schema.StringAttribute{ + Optional: true, + }, + }, Blocks: map[string]schema.Block{ "community": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ @@ -184,36 +234,60 @@ func upgradePolicyoptionsPolicyStatementStateV0toV1( From []policyoptionsPolicyStatementBlockFrom `tfsdk:"from"` To []policyoptionsPolicyStatementBlockTo `tfsdk:"to"` Then []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"` + 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 []struct { + Action types.String `tfsdk:"action"` + Value types.String `tfsdk:"value"` + } `tfsdk:"community"` + LocalPreference []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"local_preference"` + Metric []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"metric"` + Preference []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"preference"` } `tfsdk:"then"` Term []struct { Name types.String `tfsdk:"name"` From []policyoptionsPolicyStatementBlockFrom `tfsdk:"from"` To []policyoptionsPolicyStatementBlockTo `tfsdk:"to"` Then []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"` + 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 []struct { + Action types.String `tfsdk:"action"` + Value types.String `tfsdk:"value"` + } `tfsdk:"community"` + LocalPreference []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"local_preference"` + Metric []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"metric"` + Preference []struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` + } `tfsdk:"preference"` } `tfsdk:"then"` } `tfsdk:"term"` } @@ -247,16 +321,32 @@ func upgradePolicyoptionsPolicyStatementStateV0toV1( Next: dataV0.Then[0].Next, NextHop: dataV0.Then[0].NextHop, Origin: dataV0.Then[0].Origin, - Community: dataV0.Then[0].Community, + } + for _, blockV0 := range dataV0.Then[0].Community { + dataV1.Then.Community = append(dataV1.Then.Community, + policyoptionsPolicyStatementBlockThenBlockActionValue{ + Action: blockV0.Action, + Value: blockV0.Value, + }, + ) } if len(dataV0.Then[0].LocalPreference) > 0 { - dataV1.Then.LocalPreference = &dataV0.Then[0].LocalPreference[0] + dataV1.Then.LocalPreference = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: dataV0.Then[0].LocalPreference[0].Action, + Value: dataV0.Then[0].LocalPreference[0].Value, + } } if len(dataV0.Then[0].Metric) > 0 { - dataV1.Then.Metric = &dataV0.Then[0].Metric[0] + dataV1.Then.Metric = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: dataV0.Then[0].Metric[0].Action, + Value: dataV0.Then[0].Metric[0].Value, + } } if len(dataV0.Then[0].Preference) > 0 { - dataV1.Then.Preference = &dataV0.Then[0].Preference[0] + dataV1.Then.Preference = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: dataV0.Then[0].Preference[0].Action, + Value: dataV0.Then[0].Preference[0].Value, + } } } for _, blockV0 := range dataV0.Term { @@ -279,16 +369,32 @@ func upgradePolicyoptionsPolicyStatementStateV0toV1( Next: blockV0.Then[0].Next, NextHop: blockV0.Then[0].NextHop, Origin: blockV0.Then[0].Origin, - Community: blockV0.Then[0].Community, + } + for _, subBlockV0 := range blockV0.Then[0].Community { + blockV1.Then.Community = append(blockV1.Then.Community, + policyoptionsPolicyStatementBlockThenBlockActionValue{ + Action: subBlockV0.Action, + Value: subBlockV0.Value, + }, + ) } if len(blockV0.Then[0].LocalPreference) > 0 { - blockV1.Then.LocalPreference = &blockV0.Then[0].LocalPreference[0] + blockV1.Then.LocalPreference = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: blockV0.Then[0].LocalPreference[0].Action, + Value: blockV0.Then[0].LocalPreference[0].Value, + } } if len(blockV0.Then[0].Metric) > 0 { - blockV1.Then.Metric = &blockV0.Then[0].Metric[0] + blockV1.Then.Metric = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: blockV0.Then[0].Metric[0].Action, + Value: blockV0.Then[0].Metric[0].Value, + } } if len(dataV0.Then[0].Preference) > 0 { - blockV1.Then.Preference = &blockV0.Then[0].Preference[0] + blockV1.Then.Preference = &policyoptionsPolicyStatementBlockThenBlockActionValueInt64{ + Action: blockV0.Then[0].Preference[0].Action, + Value: blockV0.Then[0].Preference[0].Value, + } } } dataV1.Term = append(dataV1.Term, blockV1) From 856f312c4dba21a7935b706c3a8aa7bdbc84ecc9 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 29 Aug 2023 09:43:41 +0200 Subject: [PATCH 34/48] r/interface_logical: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_interface_logical.go | 288 ++++++++++++++++-- 1 file changed, 259 insertions(+), 29 deletions(-) diff --git a/internal/providerfwk/upgradestate_interface_logical.go b/internal/providerfwk/upgradestate_interface_logical.go index 8a8ae594..3eecb7fa 100644 --- a/internal/providerfwk/upgradestate_interface_logical.go +++ b/internal/providerfwk/upgradestate_interface_logical.go @@ -460,27 +460,128 @@ func upgradeInterfaceLogicalV0toV1( SecurityZone types.String `tfsdk:"security_zone"` VlanID types.Int64 `tfsdk:"vlan_id"` FamilyInet []struct { - SamplingInput types.Bool `tfsdk:"sampling_input"` - SamplingOutput types.Bool `tfsdk:"sampling_output"` - FilterInput types.String `tfsdk:"filter_input"` - FilterOutput types.String `tfsdk:"filter_output"` - Mtu types.Int64 `tfsdk:"mtu"` - Address []interfaceLogicalBlockFamilyInetBlockAddress `tfsdk:"address"` - DHCP []interfaceLogicalBlockFamilyInetBlockDhcp `tfsdk:"dhcp"` - RPFCheck []interfaceLogicalBlockFamilyBlockRPFCheck `tfsdk:"rpf_check"` + SamplingInput types.Bool `tfsdk:"sampling_input"` + SamplingOutput types.Bool `tfsdk:"sampling_output"` + FilterInput types.String `tfsdk:"filter_input"` + FilterOutput types.String `tfsdk:"filter_output"` + Mtu types.Int64 `tfsdk:"mtu"` + Address []struct { + Preferred types.Bool `tfsdk:"preferred"` + Primary types.Bool `tfsdk:"primary"` + CidrIP types.String `tfsdk:"cidr_ip"` + VRRPGroup []struct { + AcceptData types.Bool `tfsdk:"accept_data"` + NoAcceptData types.Bool `tfsdk:"no_accept_data"` + Preempt types.Bool `tfsdk:"preempt"` + NoPreempt types.Bool `tfsdk:"no_preempt"` + Identifier types.Int64 `tfsdk:"identifier"` + VirtualAddress []types.String `tfsdk:"virtual_address"` + AdvertiseInterval types.Int64 `tfsdk:"advertise_interval"` + AdvertisementsThreshold types.Int64 `tfsdk:"advertisements_threshold"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationType types.String `tfsdk:"authentication_type"` + Priority types.Int64 `tfsdk:"priority"` + TrackInterface []struct { + Interface types.String `tfsdk:"interface"` + PriorityCost types.Int64 `tfsdk:"priority_cost"` + } `tfsdk:"track_interface"` + TrackRoute []struct { + Route types.String `tfsdk:"route"` + RoutingInstance types.String `tfsdk:"routing_instance"` + PriorityCost types.Int64 `tfsdk:"priority_cost"` + } `tfsdk:"track_route"` + } `tfsdk:"vrrp_group"` + } `tfsdk:"address"` + DHCP []struct { + SrxOldOptionName types.Bool `tfsdk:"srx_old_option_name"` + ClientIdentifierPrefixHostname types.Bool `tfsdk:"client_identifier_prefix_hostname"` + ClientIdentifierPrefixRoutingInstanceName types.Bool `tfsdk:"client_identifier_prefix_routing_instance_name"` + ForceDiscover types.Bool `tfsdk:"force_discover"` + LeaseTimeInfinite types.Bool `tfsdk:"lease_time_infinite"` + NoDNSInstall types.Bool `tfsdk:"no_dns_install"` + OptionsNoHostname types.Bool `tfsdk:"options_no_hostname"` + UpdateServer types.Bool `tfsdk:"update_server"` + ClientIdentifierASCII types.String `tfsdk:"client_identifier_ascii"` + ClientIdentifierHexadecimal types.String `tfsdk:"client_identifier_hexadecimal"` + ClientIdentifierUseInterfaceDescription types.String `tfsdk:"client_identifier_use_interface_description"` + ClientIdentifierUseridASCII types.String `tfsdk:"client_identifier_userid_ascii"` + ClientIdentifierUseridHexadecimal types.String `tfsdk:"client_identifier_userid_hexadecimal"` + LeaseTime types.Int64 `tfsdk:"lease_time"` + Metric types.Int64 `tfsdk:"metric"` + RetransmissionAttempt types.Int64 `tfsdk:"retransmission_attempt"` + RetransmissionInterval types.Int64 `tfsdk:"retransmission_interval"` + ServerAddress types.String `tfsdk:"server_address"` + VendorID types.String `tfsdk:"vendor_id"` + } `tfsdk:"dhcp"` + RPFCheck []struct { + FailFilter types.String `tfsdk:"fail_filter"` + ModeLoose types.Bool `tfsdk:"mode_loose"` + } `tfsdk:"rpf_check"` } `tfsdk:"family_inet"` FamilyInet6 []struct { - DadDisable types.Bool `tfsdk:"dad_disable"` - SamplingInput types.Bool `tfsdk:"sampling_input"` - SamplingOutput types.Bool `tfsdk:"sampling_output"` - FilterInput types.String `tfsdk:"filter_input"` - FilterOutput types.String `tfsdk:"filter_output"` - Mtu types.Int64 `tfsdk:"mtu"` - Address []interfaceLogicalBlockFamilyInet6BlockAddress `tfsdk:"address"` - DHCPv6Client []interfaceLogicalBlockFamilyInet6BlockDhcpV6Client `tfsdk:"dhcpv6_client"` - RPFCheck []interfaceLogicalBlockFamilyBlockRPFCheck `tfsdk:"rpf_check"` + DadDisable types.Bool `tfsdk:"dad_disable"` + SamplingInput types.Bool `tfsdk:"sampling_input"` + SamplingOutput types.Bool `tfsdk:"sampling_output"` + FilterInput types.String `tfsdk:"filter_input"` + FilterOutput types.String `tfsdk:"filter_output"` + Mtu types.Int64 `tfsdk:"mtu"` + Address []struct { + Preferred types.Bool `tfsdk:"preferred"` + Primary types.Bool `tfsdk:"primary"` + CidrIP types.String `tfsdk:"cidr_ip"` + VRRPGroup []struct { + AcceptData types.Bool `tfsdk:"accept_data"` + NoAcceptData types.Bool `tfsdk:"no_accept_data"` + Preempt types.Bool `tfsdk:"preempt"` + NoPreempt types.Bool `tfsdk:"no_preempt"` + Identifier types.Int64 `tfsdk:"identifier"` + VirtualAddress []types.String `tfsdk:"virtual_address"` + VirutalLinkLocalAddress types.String `tfsdk:"virtual_link_local_address"` + AdvertiseInterval types.Int64 `tfsdk:"advertise_interval"` + AdvertisementsThreshold types.Int64 `tfsdk:"advertisements_threshold"` + Priority types.Int64 `tfsdk:"priority"` + TrackInterface []struct { + Interface types.String `tfsdk:"interface"` + PriorityCost types.Int64 `tfsdk:"priority_cost"` + } `tfsdk:"track_interface"` + TrackRoute []struct { + Route types.String `tfsdk:"route"` + RoutingInstance types.String `tfsdk:"routing_instance"` + PriorityCost types.Int64 `tfsdk:"priority_cost"` + } `tfsdk:"track_route"` + } `tfsdk:"vrrp_group"` + } `tfsdk:"address"` + DHCPv6Client []struct { + ClientIATypeNA types.Bool `tfsdk:"client_ia_type_na"` + ClientIATypePD types.Bool `tfsdk:"client_ia_type_pd"` + NoDNSInstall types.Bool `tfsdk:"no_dns_install"` + RapidCommit types.Bool `tfsdk:"rapid_commit"` + ClientIdentifierDuidType types.String `tfsdk:"client_identifier_duid_type"` + ClientType types.String `tfsdk:"client_type"` + PrefixDelegatingPreferredPrefixLength types.Int64 `tfsdk:"prefix_delegating_preferred_prefix_length"` + PrefixDelegatingSubPrefixLength types.Int64 `tfsdk:"prefix_delegating_sub_prefix_length"` + ReqOption []types.String `tfsdk:"req_option"` + RetransmissionAttempt types.Int64 `tfsdk:"retransmission_attempt"` + UpdateRouterAdvertisementInterface []types.String `tfsdk:"update_router_advertisement_interface"` + UpdateServer types.Bool `tfsdk:"update_server"` + } `tfsdk:"dhcpv6_client"` + RPFCheck []struct { + FailFilter types.String `tfsdk:"fail_filter"` + ModeLoose types.Bool `tfsdk:"mode_loose"` + } `tfsdk:"rpf_check"` } `tfsdk:"family_inet6"` - Tunnel []interfaceLogicalBlockTunnel `tfsdk:"tunnel"` + Tunnel []struct { + AllowFragmentation types.Bool `tfsdk:"allow_fragmentation"` + DoNotFragment types.Bool `tfsdk:"do_not_fragment"` + PathMtuDiscovery types.Bool `tfsdk:"path_mtu_discovery"` + NoPathMtuDiscovery types.Bool `tfsdk:"no_path_mtu_discovery"` + Destination types.String `tfsdk:"destination"` + Source types.String `tfsdk:"source"` + FlowLabel types.Int64 `tfsdk:"flow_label"` + RoutingInstanceDestination types.String `tfsdk:"routing_instance_destination"` + TrafficClass types.Int64 `tfsdk:"traffic_class"` + TTL types.Int64 `tfsdk:"ttl"` + } `tfsdk:"tunnel"` } var dataV0 modelV0 @@ -509,39 +610,168 @@ func upgradeInterfaceLogicalV0toV1( } if len(dataV0.FamilyInet) > 0 { dataV1.FamilyInet = &interfaceLogicalBlockFamilyInet{ + SamplingInput: dataV0.FamilyInet[0].SamplingInput, + SamplingOutput: dataV0.FamilyInet[0].SamplingOutput, FilterInput: dataV0.FamilyInet[0].FilterInput, FilterOutput: dataV0.FamilyInet[0].FilterOutput, Mtu: dataV0.FamilyInet[0].Mtu, - SamplingInput: dataV0.FamilyInet[0].SamplingInput, - SamplingOutput: dataV0.FamilyInet[0].SamplingOutput, - Address: dataV0.FamilyInet[0].Address, + } + for _, blockV0 := range dataV0.FamilyInet[0].Address { + blockV1 := interfaceLogicalBlockFamilyInetBlockAddress{ + Preferred: blockV0.Preferred, + Primary: blockV0.Primary, + CidrIP: blockV0.CidrIP, + } + for _, subBlockV0 := range blockV0.VRRPGroup { + subBlockV1 := interfaceLogicalBlockFamilyInetBlockAddressBlockVRRPGroup{ + AcceptData: subBlockV0.AcceptData, + NoAcceptData: subBlockV0.NoAcceptData, + Preempt: subBlockV0.Preempt, + NoPreempt: subBlockV0.NoPreempt, + Identifier: subBlockV0.Identifier, + VirtualAddress: subBlockV0.VirtualAddress, + AdvertiseInterval: subBlockV0.AdvertiseInterval, + AdvertisementsThreshold: subBlockV0.AdvertisementsThreshold, + AuthenticationKey: subBlockV0.AuthenticationKey, + AuthenticationType: subBlockV0.AuthenticationType, + Priority: subBlockV0.Priority, + } + for _, subSubBlockV0 := range subBlockV0.TrackInterface { + subBlockV1.TrackInterface = append(subBlockV1.TrackInterface, + interfaceLogicalBlockFamilyBlockAddressBlockVRRPGroupBlockTrackInterface{ + Interface: subSubBlockV0.Interface, + PriorityCost: subSubBlockV0.PriorityCost, + }, + ) + } + for _, subSubBlockV0 := range subBlockV0.TrackRoute { + subBlockV1.TrackRoute = append(subBlockV1.TrackRoute, + interfaceLogicalBlockFamilyBlockAddressBlockVRRPGroupBlockTrackRoute{ + Route: subSubBlockV0.Route, + RoutingInstance: subSubBlockV0.RoutingInstance, + PriorityCost: subSubBlockV0.PriorityCost, + }, + ) + } + blockV1.VRRPGroup = append(blockV1.VRRPGroup, subBlockV1) + } + dataV1.FamilyInet.Address = append(dataV1.FamilyInet.Address, blockV1) } if len(dataV0.FamilyInet[0].DHCP) > 0 { - dataV1.FamilyInet.DHCP = &dataV0.FamilyInet[0].DHCP[0] + dataV1.FamilyInet.DHCP = &interfaceLogicalBlockFamilyInetBlockDhcp{ + SrxOldOptionName: dataV0.FamilyInet[0].DHCP[0].SrxOldOptionName, + ClientIdentifierPrefixHostname: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierPrefixHostname, + ClientIdentifierPrefixRoutingInstanceName: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierPrefixRoutingInstanceName, + ForceDiscover: dataV0.FamilyInet[0].DHCP[0].ForceDiscover, + LeaseTimeInfinite: dataV0.FamilyInet[0].DHCP[0].LeaseTimeInfinite, + NoDNSInstall: dataV0.FamilyInet[0].DHCP[0].NoDNSInstall, + OptionsNoHostname: dataV0.FamilyInet[0].DHCP[0].OptionsNoHostname, + UpdateServer: dataV0.FamilyInet[0].DHCP[0].UpdateServer, + ClientIdentifierASCII: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierASCII, + ClientIdentifierHexadecimal: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierHexadecimal, + ClientIdentifierUseInterfaceDescription: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierUseInterfaceDescription, + ClientIdentifierUseridASCII: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierUseridASCII, + ClientIdentifierUseridHexadecimal: dataV0.FamilyInet[0].DHCP[0].ClientIdentifierUseridHexadecimal, + LeaseTime: dataV0.FamilyInet[0].DHCP[0].LeaseTime, + Metric: dataV0.FamilyInet[0].DHCP[0].Metric, + RetransmissionAttempt: dataV0.FamilyInet[0].DHCP[0].RetransmissionAttempt, + RetransmissionInterval: dataV0.FamilyInet[0].DHCP[0].RetransmissionInterval, + ServerAddress: dataV0.FamilyInet[0].DHCP[0].ServerAddress, + VendorID: dataV0.FamilyInet[0].DHCP[0].VendorID, + } } if len(dataV0.FamilyInet[0].RPFCheck) > 0 { - dataV1.FamilyInet.RPFCheck = &dataV0.FamilyInet[0].RPFCheck[0] + dataV1.FamilyInet.RPFCheck = &interfaceLogicalBlockFamilyBlockRPFCheck{ + FailFilter: dataV0.FamilyInet[0].RPFCheck[0].FailFilter, + ModeLoose: dataV0.FamilyInet[0].RPFCheck[0].ModeLoose, + } } } if len(dataV0.FamilyInet6) > 0 { dataV1.FamilyInet6 = &interfaceLogicalBlockFamilyInet6{ DadDisable: dataV0.FamilyInet6[0].DadDisable, + SamplingInput: dataV0.FamilyInet6[0].SamplingInput, + SamplingOutput: dataV0.FamilyInet6[0].SamplingOutput, FilterInput: dataV0.FamilyInet6[0].FilterInput, FilterOutput: dataV0.FamilyInet6[0].FilterOutput, Mtu: dataV0.FamilyInet6[0].Mtu, - SamplingInput: dataV0.FamilyInet6[0].SamplingInput, - SamplingOutput: dataV0.FamilyInet6[0].SamplingOutput, - Address: dataV0.FamilyInet6[0].Address, + } + for _, blockV0 := range dataV0.FamilyInet6[0].Address { + blockV1 := interfaceLogicalBlockFamilyInet6BlockAddress{ + Preferred: blockV0.Preferred, + Primary: blockV0.Primary, + CidrIP: blockV0.CidrIP, + } + for _, subBlockV0 := range blockV0.VRRPGroup { + subBlockV1 := interfaceLogicalBlockFamilyInet6BlockAddressBlockVRRPGroup{ + AcceptData: subBlockV0.AcceptData, + NoAcceptData: subBlockV0.NoAcceptData, + Preempt: subBlockV0.Preempt, + NoPreempt: subBlockV0.NoPreempt, + Identifier: subBlockV0.Identifier, + VirtualAddress: subBlockV0.VirtualAddress, + VirutalLinkLocalAddress: subBlockV0.VirutalLinkLocalAddress, + AdvertiseInterval: subBlockV0.AdvertiseInterval, + AdvertisementsThreshold: subBlockV0.AdvertisementsThreshold, + Priority: subBlockV0.Priority, + } + for _, subSubBlockV0 := range subBlockV0.TrackInterface { + subBlockV1.TrackInterface = append(subBlockV1.TrackInterface, + interfaceLogicalBlockFamilyBlockAddressBlockVRRPGroupBlockTrackInterface{ + Interface: subSubBlockV0.Interface, + PriorityCost: subSubBlockV0.PriorityCost, + }, + ) + } + for _, subSubBlockV0 := range subBlockV0.TrackRoute { + subBlockV1.TrackRoute = append(subBlockV1.TrackRoute, + interfaceLogicalBlockFamilyBlockAddressBlockVRRPGroupBlockTrackRoute{ + Route: subSubBlockV0.Route, + RoutingInstance: subSubBlockV0.RoutingInstance, + PriorityCost: subSubBlockV0.PriorityCost, + }, + ) + } + blockV1.VRRPGroup = append(blockV1.VRRPGroup, subBlockV1) + } + dataV1.FamilyInet6.Address = append(dataV1.FamilyInet6.Address, blockV1) } if len(dataV0.FamilyInet6[0].DHCPv6Client) > 0 { - dataV1.FamilyInet6.DHCPv6Client = &dataV0.FamilyInet6[0].DHCPv6Client[0] + dataV1.FamilyInet6.DHCPv6Client = &interfaceLogicalBlockFamilyInet6BlockDhcpV6Client{ + ClientIATypeNA: dataV0.FamilyInet6[0].DHCPv6Client[0].ClientIATypeNA, + ClientIATypePD: dataV0.FamilyInet6[0].DHCPv6Client[0].ClientIATypePD, + NoDNSInstall: dataV0.FamilyInet6[0].DHCPv6Client[0].NoDNSInstall, + RapidCommit: dataV0.FamilyInet6[0].DHCPv6Client[0].RapidCommit, + ClientIdentifierDuidType: dataV0.FamilyInet6[0].DHCPv6Client[0].ClientIdentifierDuidType, + ClientType: dataV0.FamilyInet6[0].DHCPv6Client[0].ClientType, + PrefixDelegatingPreferredPrefixLength: dataV0.FamilyInet6[0].DHCPv6Client[0].PrefixDelegatingPreferredPrefixLength, + PrefixDelegatingSubPrefixLength: dataV0.FamilyInet6[0].DHCPv6Client[0].PrefixDelegatingSubPrefixLength, + ReqOption: dataV0.FamilyInet6[0].DHCPv6Client[0].ReqOption, + RetransmissionAttempt: dataV0.FamilyInet6[0].DHCPv6Client[0].RetransmissionAttempt, + UpdateRouterAdvertisementInterface: dataV0.FamilyInet6[0].DHCPv6Client[0].UpdateRouterAdvertisementInterface, + UpdateServer: dataV0.FamilyInet6[0].DHCPv6Client[0].UpdateServer, + } } if len(dataV0.FamilyInet6[0].RPFCheck) > 0 { - dataV1.FamilyInet6.RPFCheck = &dataV0.FamilyInet6[0].RPFCheck[0] + dataV1.FamilyInet6.RPFCheck = &interfaceLogicalBlockFamilyBlockRPFCheck{ + FailFilter: dataV0.FamilyInet6[0].RPFCheck[0].FailFilter, + ModeLoose: dataV0.FamilyInet6[0].RPFCheck[0].ModeLoose, + } } } if len(dataV0.Tunnel) > 0 { - dataV1.Tunnel = &dataV0.Tunnel[0] + dataV1.Tunnel = &interfaceLogicalBlockTunnel{ + AllowFragmentation: dataV0.Tunnel[0].AllowFragmentation, + DoNotFragment: dataV0.Tunnel[0].DoNotFragment, + PathMtuDiscovery: dataV0.Tunnel[0].PathMtuDiscovery, + NoPathMtuDiscovery: dataV0.Tunnel[0].NoPathMtuDiscovery, + Destination: dataV0.Tunnel[0].Destination, + Source: dataV0.Tunnel[0].Source, + FlowLabel: dataV0.Tunnel[0].FlowLabel, + RoutingInstanceDestination: dataV0.Tunnel[0].RoutingInstanceDestination, + TrafficClass: dataV0.Tunnel[0].TrafficClass, + TTL: dataV0.Tunnel[0].TTL, + } } resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) From 59dffe27a16f9cb00193dac3f89ce347ee8f5a99 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 29 Aug 2023 09:46:54 +0200 Subject: [PATCH 35/48] r/interface_physical: upgradestate: avoid using block struct from the new version of resource schema when reading state with old version schema to prevent potential `Value Conversion Error` after adding an argument in block --- .../upgradestate_interface_physical.go | 155 ++++++++++++++---- 1 file changed, 123 insertions(+), 32 deletions(-) diff --git a/internal/providerfwk/upgradestate_interface_physical.go b/internal/providerfwk/upgradestate_interface_physical.go index bc6bbd1f..70e5eabb 100644 --- a/internal/providerfwk/upgradestate_interface_physical.go +++ b/internal/providerfwk/upgradestate_interface_physical.go @@ -245,37 +245,82 @@ func (rsc *interfacePhysical) UpgradeState(_ context.Context) map[int64]resource } } -//nolint:lll func upgradeInterfacePhysicalV0toV1( ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, ) { type modelV0 struct { - NoDisableOnDestroy types.Bool `tfsdk:"no_disable_on_destroy"` - Disable types.Bool `tfsdk:"disable"` - Trunk types.Bool `tfsdk:"trunk"` - VlanTagging types.Bool `tfsdk:"vlan_tagging"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - Mtu types.Int64 `tfsdk:"mtu"` - VlanMembers []types.String `tfsdk:"vlan_members"` - VlanNative types.Int64 `tfsdk:"vlan_native"` - ESI []interfacePhysicalBlockESI `tfsdk:"esi"` - EtherOpts []interfacePhysicalBlockEtherOpts `tfsdk:"ether_opts"` - GigetherOpts []interfacePhysicalBlockEtherOpts `tfsdk:"gigether_opts"` - ParentEtherOpts []struct { - FlowControl types.Bool `tfsdk:"flow_control"` - NoFlowControl types.Bool `tfsdk:"no_flow_control"` - Loopback types.Bool `tfsdk:"loopback"` - NoLoopback types.Bool `tfsdk:"no_loopback"` - SourceFiltering types.Bool `tfsdk:"source_filtering"` - LinkSpeed types.String `tfsdk:"link_speed"` - MinimumBandwidth types.String `tfsdk:"minimum_bandwidth"` - MinimumLinks types.Int64 `tfsdk:"minimum_links"` - RedundancyGroup types.Int64 `tfsdk:"redundancy_group"` - SourceAddressFilter []types.String `tfsdk:"source_address_filter"` - BFDLivenessDetection []interfacePhysicalBlockParentEtherOptsBlockBFDLivenessDetection `tfsdk:"bfd_liveness_detection"` - Lacp []interfacePhysicalBlockParentEtherOptsBlockLacp `tfsdk:"lacp"` + NoDisableOnDestroy types.Bool `tfsdk:"no_disable_on_destroy"` + Disable types.Bool `tfsdk:"disable"` + Trunk types.Bool `tfsdk:"trunk"` + VlanTagging types.Bool `tfsdk:"vlan_tagging"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Mtu types.Int64 `tfsdk:"mtu"` + VlanMembers []types.String `tfsdk:"vlan_members"` + VlanNative types.Int64 `tfsdk:"vlan_native"` + ESI []struct { + AutoDeriveLacp types.Bool `tfsdk:"auto_derive_lacp"` + Mode types.String `tfsdk:"mode"` + DFElectionType types.String `tfsdk:"df_election_type"` + Identifier types.String `tfsdk:"identifier"` + SourceBMAC types.String `tfsdk:"source_bmac"` + } `tfsdk:"esi"` + EtherOpts []struct { + AutoNegotiation types.Bool `tfsdk:"auto_negotiation"` + NoAutoNegotiation types.Bool `tfsdk:"no_auto_negotiation"` + FlowControl types.Bool `tfsdk:"flow_control"` + NoFlowControl types.Bool `tfsdk:"no_flow_control"` + Loopback types.Bool `tfsdk:"loopback"` + NoLoopback types.Bool `tfsdk:"no_loopback"` + Ae8023ad types.String `tfsdk:"ae_8023ad"` + RedundantParent types.String `tfsdk:"redundant_parent"` + } `tfsdk:"ether_opts"` + GigetherOpts []struct { + AutoNegotiation types.Bool `tfsdk:"auto_negotiation"` + NoAutoNegotiation types.Bool `tfsdk:"no_auto_negotiation"` + FlowControl types.Bool `tfsdk:"flow_control"` + NoFlowControl types.Bool `tfsdk:"no_flow_control"` + Loopback types.Bool `tfsdk:"loopback"` + NoLoopback types.Bool `tfsdk:"no_loopback"` + Ae8023ad types.String `tfsdk:"ae_8023ad"` + RedundantParent types.String `tfsdk:"redundant_parent"` + } `tfsdk:"gigether_opts"` + ParentEtherOpts []struct { + FlowControl types.Bool `tfsdk:"flow_control"` + NoFlowControl types.Bool `tfsdk:"no_flow_control"` + Loopback types.Bool `tfsdk:"loopback"` + NoLoopback types.Bool `tfsdk:"no_loopback"` + SourceFiltering types.Bool `tfsdk:"source_filtering"` + LinkSpeed types.String `tfsdk:"link_speed"` + MinimumBandwidth types.String `tfsdk:"minimum_bandwidth"` + MinimumLinks types.Int64 `tfsdk:"minimum_links"` + RedundancyGroup types.Int64 `tfsdk:"redundancy_group"` + SourceAddressFilter []types.String `tfsdk:"source_address_filter"` + BFDLivenessDetection []struct { + AuthenticationLooseCheck types.Bool `tfsdk:"authentication_loose_check"` + NoAdaptation types.Bool `tfsdk:"no_adaptation"` + LocalAddress types.String `tfsdk:"local_address"` + 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"` + Neighbor types.String `tfsdk:"neighbor"` + TransmitIntervalMinimumInterval types.Int64 `tfsdk:"transmit_interval_minimum_interval"` + TransmitIntervalThreshold types.Int64 `tfsdk:"transmit_interval_threshold"` + Version types.String `tfsdk:"version"` + } `tfsdk:"bfd_liveness_detection"` + Lacp []struct { + Mode types.String `tfsdk:"mode"` + AdminKey types.Int64 `tfsdk:"admin_key"` + Periodic types.String `tfsdk:"periodic"` + SyncReset types.String `tfsdk:"sync_reset"` + SystemID types.String `tfsdk:"system_id"` + SystemPriority types.Int64 `tfsdk:"system_priority"` + } `tfsdk:"lacp"` } `tfsdk:"parent_ether_opts"` } @@ -300,13 +345,37 @@ func upgradeInterfacePhysicalV0toV1( dataV1.VlanNative = dataV0.VlanNative dataV1.VlanTagging = dataV0.VlanTagging if len(dataV0.ESI) > 0 { - dataV1.ESI = &dataV0.ESI[0] + dataV1.ESI = &interfacePhysicalBlockESI{ + AutoDeriveLacp: dataV0.ESI[0].AutoDeriveLacp, + Mode: dataV0.ESI[0].Mode, + DFElectionType: dataV0.ESI[0].DFElectionType, + Identifier: dataV0.ESI[0].Identifier, + SourceBMAC: dataV0.ESI[0].SourceBMAC, + } } if len(dataV0.EtherOpts) > 0 { - dataV1.EtherOpts = &dataV0.EtherOpts[0] + dataV1.EtherOpts = &interfacePhysicalBlockEtherOpts{ + AutoNegotiation: dataV0.EtherOpts[0].AutoNegotiation, + NoAutoNegotiation: dataV0.EtherOpts[0].NoAutoNegotiation, + FlowControl: dataV0.EtherOpts[0].FlowControl, + NoFlowControl: dataV0.EtherOpts[0].NoFlowControl, + Loopback: dataV0.EtherOpts[0].Loopback, + NoLoopback: dataV0.EtherOpts[0].NoLoopback, + Ae8023ad: dataV0.EtherOpts[0].Ae8023ad, + RedundantParent: dataV0.EtherOpts[0].RedundantParent, + } } if len(dataV0.GigetherOpts) > 0 { - dataV1.GigetherOpts = &dataV0.GigetherOpts[0] + dataV1.GigetherOpts = &interfacePhysicalBlockEtherOpts{ + AutoNegotiation: dataV0.GigetherOpts[0].AutoNegotiation, + NoAutoNegotiation: dataV0.GigetherOpts[0].NoAutoNegotiation, + FlowControl: dataV0.GigetherOpts[0].FlowControl, + NoFlowControl: dataV0.GigetherOpts[0].NoFlowControl, + Loopback: dataV0.GigetherOpts[0].Loopback, + NoLoopback: dataV0.GigetherOpts[0].NoLoopback, + Ae8023ad: dataV0.GigetherOpts[0].Ae8023ad, + RedundantParent: dataV0.GigetherOpts[0].RedundantParent, + } } if len(dataV0.ParentEtherOpts) > 0 { dataV1.ParentEtherOpts = &interfacePhysicalBlockParentEtherOpts{ @@ -322,10 +391,32 @@ func upgradeInterfacePhysicalV0toV1( SourceFiltering: dataV0.ParentEtherOpts[0].SourceFiltering, } if len(dataV0.ParentEtherOpts[0].BFDLivenessDetection) > 0 { - dataV1.ParentEtherOpts.BFDLivenessDetection = &dataV0.ParentEtherOpts[0].BFDLivenessDetection[0] + dataV1.ParentEtherOpts.BFDLivenessDetection = &interfacePhysicalBlockParentEtherOptsBlockBFDLivenessDetection{ + AuthenticationLooseCheck: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].AuthenticationLooseCheck, + NoAdaptation: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].NoAdaptation, + LocalAddress: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].LocalAddress, + AuthenticationAlgorithm: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].AuthenticationAlgorithm, + AuthenticationKeyChain: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].AuthenticationKeyChain, + DetectionTimeThreshold: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].DetectionTimeThreshold, + HolddownInterval: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].HolddownInterval, + MinimumInterval: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].MinimumInterval, + MinimumReceiveInterval: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].MinimumReceiveInterval, + Multiplier: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].Multiplier, + Neighbor: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].Neighbor, + TransmitIntervalMinimumInterval: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].TransmitIntervalMinimumInterval, + TransmitIntervalThreshold: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].TransmitIntervalThreshold, + Version: dataV0.ParentEtherOpts[0].BFDLivenessDetection[0].Version, + } } if len(dataV0.ParentEtherOpts[0].Lacp) > 0 { - dataV1.ParentEtherOpts.Lacp = &dataV0.ParentEtherOpts[0].Lacp[0] + dataV1.ParentEtherOpts.Lacp = &interfacePhysicalBlockParentEtherOptsBlockLacp{ + Mode: dataV0.ParentEtherOpts[0].Lacp[0].Mode, + AdminKey: dataV0.ParentEtherOpts[0].Lacp[0].AdminKey, + Periodic: dataV0.ParentEtherOpts[0].Lacp[0].Periodic, + SyncReset: dataV0.ParentEtherOpts[0].Lacp[0].SyncReset, + SystemID: dataV0.ParentEtherOpts[0].Lacp[0].SystemID, + SystemPriority: dataV0.ParentEtherOpts[0].Lacp[0].SystemPriority, + } } } From f8b6843354b970128ff585c5546c6e914daef5b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:57:11 +0000 Subject: [PATCH 36/48] deps: bump github.com/hashicorp/terraform-plugin-framework-validators Bumps [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators) from 0.11.0 to 0.12.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-framework-validators/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-framework-validators/compare/v0.11.0...v0.12.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-framework-validators dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f35e0943..86c8f296 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-framework v1.3.5 - github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-mux v0.11.2 github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0 diff --git a/go.sum b/go.sum index fb144229..a43ba7a1 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQH github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= github.com/hashicorp/terraform-plugin-framework v1.3.5 h1:FJ6s3CVWVAxlhiF/jhy6hzs4AnPHiflsp9KgzTGl1wo= github.com/hashicorp/terraform-plugin-framework v1.3.5/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= -github.com/hashicorp/terraform-plugin-framework-validators v0.11.0 h1:DKb1bX7/EPZUTW6F5zdwJzS/EZ/ycVD6JAW5RYOj4f8= -github.com/hashicorp/terraform-plugin-framework-validators v0.11.0/go.mod h1:dzxOiHh7O9CAwc6p8N4mR1H++LtRkl+u+21YNiBVNno= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= github.com/hashicorp/terraform-plugin-go v0.18.0/go.mod h1:l7VK+2u5Kf2y+A+742GX0ouLut3gttudmvMgN0PA74Y= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= From ab6f431cc4842d4b0d9729df2ea711b224b0a2d0 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 31 Aug 2023 12:22:34 +0200 Subject: [PATCH 37/48] r/aggregate_route: use new provider via framework --- .changes/route-with-fwk.md | 6 + docs/resources/aggregate_route.md | 8 +- internal/junos/constants.go | 3 + internal/providerfwk/provider.go | 1 + .../providerfwk/resource_aggregate_route.go | 708 ++++++++++++++++++ .../resource_aggregate_route_test.go | 4 +- ...resource_policyoptions_policy_statement.go | 2 +- .../providerfwk/resource_routing_instance.go | 24 +- internal/providersdk/provider.go | 1 - .../providersdk/resource_aggregate_route.go | 631 ---------------- .../providersdk/resource_group_dual_system.go | 4 +- internal/providersdk/resource_rib_group.go | 5 +- 12 files changed, 742 insertions(+), 655 deletions(-) create mode 100644 .changes/route-with-fwk.md create mode 100644 internal/providerfwk/resource_aggregate_route.go rename internal/{providersdk => providerfwk}/resource_aggregate_route_test.go (98%) delete mode 100644 internal/providersdk/resource_aggregate_route.go diff --git a/.changes/route-with-fwk.md b/.changes/route-with-fwk.md new file mode 100644 index 00000000..8627a60d --- /dev/null +++ b/.changes/route-with-fwk.md @@ -0,0 +1,6 @@ + +ENHANCEMENTS: + +* **resource/junos_aggregate_route**: 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/aggregate_route.md b/docs/resources/aggregate_route.md index 47fe9296..37a25202 100644 --- a/docs/resources/aggregate_route.md +++ b/docs/resources/aggregate_route.md @@ -22,9 +22,9 @@ resource "junos_aggregate_route" "demo_aggregate_route" { The following arguments are supported: - **destination** (Required, String, Forces new resource) - The destination for aggregate route. + Destination prefix. - **routing_instance** (Optional, String, Forces new resource) - Routing instance for route. + Routing instance for aggregate route. Need to be `default` or name of routing instance. Defaults to `default` - **active** (Optional, Boolean) @@ -43,7 +43,7 @@ The following arguments are supported: - **brief** (Optional, Boolean) Include longest common sequences from contributing paths. - **community** (Optional, List of String) - List of BGP community. + BGP community. - **discard** (Optional, Boolean) Drop packets to destination; send no ICMP unreachables. - **full** (Optional, Boolean) @@ -53,7 +53,7 @@ The following arguments are supported: - **passive** (Optional, Boolean) Retain inactive route in forwarding table. - **policy** (Optional, List of String) - List of Policy filter. + Policy filter. - **preference** (Optional, Number) Preference for aggregate route. diff --git a/internal/junos/constants.go b/internal/junos/constants.go index 8c2aa4da..d6b10ae5 100644 --- a/internal/junos/constants.go +++ b/internal/junos/constants.go @@ -17,6 +17,9 @@ const ( SetRoutingInstances = SetLS + RoutingInstancesWS DelRoutingInstances = DeleteLS + RoutingInstancesWS + RoutingOptionsWS = "routing-options " + RibInet60WS = "rib inet6.0 " + EmptyW = "empty" PermitW = "permit" DiscardW = "discard" diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 814ff0ef..4ace52b8 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -199,6 +199,7 @@ func (p *junosProvider) DataSources(_ context.Context) []func() datasource.DataS func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ + newAggregateRouteResource, newApplicationSetResource, newApplicationResource, newBgpGroupResource, diff --git a/internal/providerfwk/resource_aggregate_route.go b/internal/providerfwk/resource_aggregate_route.go new file mode 100644 index 00000000..34795c3e --- /dev/null +++ b/internal/providerfwk/resource_aggregate_route.go @@ -0,0 +1,708 @@ +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/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 = &aggregateRoute{} + _ resource.ResourceWithConfigure = &aggregateRoute{} + _ resource.ResourceWithValidateConfig = &aggregateRoute{} + _ resource.ResourceWithImportState = &aggregateRoute{} +) + +type aggregateRoute struct { + client *junos.Client +} + +func newAggregateRouteResource() resource.Resource { + return &aggregateRoute{} +} + +func (rsc *aggregateRoute) typeName() string { + return providerName + "_aggregate_route" +} + +func (rsc *aggregateRoute) junosName() string { + return "aggregate route" +} + +func (rsc *aggregateRoute) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *aggregateRoute) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *aggregateRoute) 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 *aggregateRoute) 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 " + + "`" + junos.IDSeparator + "`.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "destination": schema.StringAttribute{ + Required: true, + Description: "Destination prefix.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(junos.DefaultW), + Description: "Routing instance for aggregate route.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "active": schema.BoolAttribute{ + Optional: true, + Description: "Remove inactive route from forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_aggregator_address": schema.StringAttribute{ + Optional: true, + Description: "Address of BGP system to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "as_path_aggregator_as_number": schema.StringAttribute{ + Optional: true, + Description: "AS number to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "as_path_atomic_aggregate": schema.BoolAttribute{ + Optional: true, + Description: "Add ATOMIC_AGGREGATE path attribute to route.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_origin": schema.StringAttribute{ + Optional: true, + Description: "Define origin.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + "as_path_path": schema.StringAttribute{ + Optional: true, + Description: "Path to as-path.", + Validators: []validator.String{ + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "brief": schema.BoolAttribute{ + Optional: true, + Description: "Include longest common sequences from contributing paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "community": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "BGP community.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "discard": schema.BoolAttribute{ + Optional: true, + Description: "Drop packets to destination; send no ICMP unreachables.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "full": schema.BoolAttribute{ + Optional: true, + Description: "Include all AS numbers from all contributing paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric for aggregate route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "passive": schema.BoolAttribute{ + Optional: true, + Description: "Retain inactive route in forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "policy": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Policy filter.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference for aggregate route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + } +} + +type aggregateRouteData struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Brief types.Bool `tfsdk:"brief"` + Discard types.Bool `tfsdk:"discard"` + Full types.Bool `tfsdk:"full"` + Passive types.Bool `tfsdk:"passive"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community []types.String `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + Policy []types.String `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` +} + +type aggregateRouteConfig struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Brief types.Bool `tfsdk:"brief"` + Discard types.Bool `tfsdk:"discard"` + Full types.Bool `tfsdk:"full"` + Passive types.Bool `tfsdk:"passive"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community types.List `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + Policy types.List `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` +} + +func (rsc *aggregateRoute) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config aggregateRouteConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.Active.IsNull() && + !config.Passive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("active"), + tfdiag.ConflictConfigErrSummary, + "active and passive cannot be configured together", + ) + } + if !config.ASPathAggregatorASNumber.IsNull() && + config.ASPathAggregatorAddress.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_as_number"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_address must be specified with as_path_aggregator_as_number", + ) + } + if !config.ASPathAggregatorAddress.IsNull() && + config.ASPathAggregatorASNumber.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_address"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_as_number must be specified with as_path_aggregator_address", + ) + } + if !config.Brief.IsNull() && + !config.Full.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("brief"), + tfdiag.ConflictConfigErrSummary, + "brief and full cannot be configured together", + ) + } +} + +func (rsc *aggregateRoute) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan aggregateRouteData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Destination.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("destination"), + "Empty Destination", + "could not create "+rsc.junosName()+" with empty destination", + ) + + return + } + + 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 + } + } + routeExists, err := checkAggregateRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + routeExists, err := checkAggregateRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *aggregateRoute) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data aggregateRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom2String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Destination.ValueString(), + state.RoutingInstance.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *aggregateRoute) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state aggregateRouteData + 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 *aggregateRoute) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state aggregateRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *aggregateRoute) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data aggregateRouteData + + 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 checkAggregateRouteExists( + _ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + _ bool, err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "aggregate route " + destination + junos.PipeDisplaySet) + if err != nil { + return false, err + } + + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *aggregateRouteData) fillID() { + if v := rscData.RoutingInstance.ValueString(); v != "" { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + v) + } else { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + junos.DefaultW) + } +} + +func (rscData *aggregateRouteData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *aggregateRouteData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := junos.SetLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + setPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += junos.RibInet60WS + } + default: + setPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + setPrefix += "aggregate route " + rscData.Destination.ValueString() + " " + configSet := []string{ + setPrefix, + } + + if rscData.Active.ValueBool() { + configSet = append(configSet, setPrefix+"active") + } + if vNumber, vAddress := rscData.ASPathAggregatorASNumber.ValueString(), + rscData.ASPathAggregatorAddress.ValueString(); vNumber != "" && vAddress != "" { + configSet = append(configSet, setPrefix+"as-path aggregator "+vNumber+" "+vAddress) + } + if rscData.ASPathAtomicAggregate.ValueBool() { + configSet = append(configSet, setPrefix+"as-path atomic-aggregate") + } + if v := rscData.ASPathOrigin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path origin "+v) + } + if v := rscData.ASPathPath.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path path \""+v+"\"") + } + if rscData.Brief.ValueBool() { + configSet = append(configSet, setPrefix+"brief") + } + for _, v := range rscData.Community { + configSet = append(configSet, setPrefix+"community \""+v.ValueString()+"\"") + } + if rscData.Discard.ValueBool() { + configSet = append(configSet, setPrefix+"discard") + } + if rscData.Full.ValueBool() { + configSet = append(configSet, setPrefix+"full") + } + if !rscData.Metric.IsNull() { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(rscData.Metric.ValueInt64())) + } + if rscData.Passive.ValueBool() { + configSet = append(configSet, setPrefix+"passive") + } + for _, v := range rscData.Policy { + configSet = append(configSet, setPrefix+"policy \""+v.ValueString()+"\"") + } + if !rscData.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(rscData.Preference.ValueInt64())) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *aggregateRouteData) read( + _ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "aggregate route " + destination + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + + if showConfig != junos.EmptyW { + rscData.Destination = types.StringValue(destination) + rscData.RoutingInstance = types.StringValue(routingInstance) + if rscData.RoutingInstance.ValueString() == "" { + rscData.RoutingInstance = types.StringValue(junos.DefaultW) + } + 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 == "active": + rscData.Active = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { //
+ return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) + } + rscData.ASPathAggregatorASNumber = types.StringValue(itemTrimFields[0]) + rscData.ASPathAggregatorAddress = types.StringValue(itemTrimFields[1]) + case itemTrim == "as-path atomic-aggregate": + rscData.ASPathAtomicAggregate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path origin "): + rscData.ASPathOrigin = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "as-path path "): + rscData.ASPathPath = types.StringValue(strings.Trim(itemTrim, "\"")) + case itemTrim == "brief": + rscData.Brief = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "community "): + rscData.Community = append(rscData.Community, types.StringValue(strings.Trim(itemTrim, "\""))) + case itemTrim == junos.DiscardW: + rscData.Discard = types.BoolValue(true) + case itemTrim == "full": + rscData.Full = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "metric "): + rscData.Metric, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "passive": + rscData.Passive = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "policy "): + rscData.Policy = append(rscData.Policy, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "preference "): + rscData.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + } + } + + return nil +} + +func (rscData *aggregateRouteData) del( + _ context.Context, junSess *junos.Session, +) error { + delPrefix := junos.DeleteLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + delPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += junos.RibInet60WS + } + default: + delPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + configSet := []string{ + delPrefix + "aggregate route " + rscData.Destination.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_aggregate_route_test.go b/internal/providerfwk/resource_aggregate_route_test.go similarity index 98% rename from internal/providersdk/resource_aggregate_route_test.go rename to internal/providerfwk/resource_aggregate_route_test.go index 23545efa..aeb556ca 100644 --- a/internal/providersdk/resource_aggregate_route_test.go +++ b/internal/providerfwk/resource_aggregate_route_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 TestAccJunosAggregateRoute_basic(t *testing.T) { diff --git a/internal/providerfwk/resource_policyoptions_policy_statement.go b/internal/providerfwk/resource_policyoptions_policy_statement.go index 610173f8..f88240db 100644 --- a/internal/providerfwk/resource_policyoptions_policy_statement.go +++ b/internal/providerfwk/resource_policyoptions_policy_statement.go @@ -2583,7 +2583,7 @@ func (rscData *policyoptionsPolicyStatementData) read( } showConfigForwardingTableExport, err := junSess.Command(junos.CmdShowConfig + - "routing-options forwarding-table export" + junos.PipeDisplaySetRelative) + junos.RoutingOptionsWS + "forwarding-table export" + junos.PipeDisplaySetRelative) if err != nil { return err } diff --git a/internal/providerfwk/resource_routing_instance.go b/internal/providerfwk/resource_routing_instance.go index 8516eb71..735910ff 100644 --- a/internal/providerfwk/resource_routing_instance.go +++ b/internal/providerfwk/resource_routing_instance.go @@ -567,19 +567,19 @@ func (rscData *routingInstanceData) set( } } if v := rscData.AS.ValueString(); v != "" { - configSet = append(configSet, setPrefix+"routing-options autonomous-system "+v) + configSet = append(configSet, setPrefix+junos.RoutingOptionsWS+"autonomous-system "+v) } if v := rscData.RouterID.ValueString(); v != "" { - configSet = append(configSet, setPrefix+"routing-options router-id "+v) + configSet = append(configSet, setPrefix+junos.RoutingOptionsWS+"router-id "+v) } if v := rscData.Description.ValueString(); v != "" { configSet = append(configSet, setPrefix+"description \""+v+"\"") } for _, v := range rscData.InstanceExport { - configSet = append(configSet, setPrefix+"routing-options instance-export \""+v.ValueString()+"\"") + configSet = append(configSet, setPrefix+junos.RoutingOptionsWS+"instance-export \""+v.ValueString()+"\"") } for _, v := range rscData.InstanceImport { - configSet = append(configSet, setPrefix+"routing-options instance-import \""+v.ValueString()+"\"") + configSet = append(configSet, setPrefix+junos.RoutingOptionsWS+"instance-import \""+v.ValueString()+"\"") } if v := rscData.VTEPSourceInterface.ValueString(); v != "" { configSet = append(configSet, setPrefix+"vtep-source-interface "+v) @@ -616,15 +616,15 @@ func (rscData *routingInstanceData) read( rscData.Type = types.StringValue(itemTrim) case balt.CutPrefixInString(&itemTrim, "route-distinguisher "): rscData.RouteDistinguisher = types.StringValue(itemTrim) - case balt.CutPrefixInString(&itemTrim, "routing-options autonomous-system "): + case balt.CutPrefixInString(&itemTrim, junos.RoutingOptionsWS+"autonomous-system "): rscData.AS = types.StringValue(itemTrim) - case balt.CutPrefixInString(&itemTrim, "routing-options instance-export "): + case balt.CutPrefixInString(&itemTrim, junos.RoutingOptionsWS+"instance-export "): rscData.InstanceExport = append(rscData.InstanceExport, types.StringValue(strings.Trim(itemTrim, "\""))) - case balt.CutPrefixInString(&itemTrim, "routing-options instance-import "): + case balt.CutPrefixInString(&itemTrim, junos.RoutingOptionsWS+"instance-import "): rscData.InstanceImport = append(rscData.InstanceImport, types.StringValue(strings.Trim(itemTrim, "\""))) - case balt.CutPrefixInString(&itemTrim, "routing-options router-id "): + case balt.CutPrefixInString(&itemTrim, junos.RoutingOptionsWS+"router-id "): rscData.RouterID = types.StringValue(itemTrim) case balt.CutPrefixInString(&itemTrim, "vrf-export "): rscData.VRFExport = append(rscData.VRFExport, @@ -659,10 +659,10 @@ func (rscData *routingInstanceData) delOpts( setPrefix := junos.DelRoutingInstances + rscData.Name.ValueString() + " " configSet = append(configSet, setPrefix+"description", - setPrefix+"routing-options autonomous-system", - setPrefix+"routing-options instance-export", - setPrefix+"routing-options instance-import", - setPrefix+"routing-options router-id", + setPrefix+junos.RoutingOptionsWS+"autonomous-system", + setPrefix+junos.RoutingOptionsWS+"instance-export", + setPrefix+junos.RoutingOptionsWS+"instance-import", + setPrefix+junos.RoutingOptionsWS+"router-id", setPrefix+"vtep-source-interface", ) if !rscData.ConfigureTypeSingly.ValueBool() { diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index f3034419..483d482c 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -146,7 +146,6 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ "junos_access_address_assignment_pool": resourceAccessAddressAssignPool(), - "junos_aggregate_route": resourceAggregateRoute(), "junos_bridge_domain": resourceBridgeDomain(), "junos_chassis_cluster": resourceChassisCluster(), "junos_chassis_redundancy": resourceChassisRedundancy(), diff --git a/internal/providersdk/resource_aggregate_route.go b/internal/providersdk/resource_aggregate_route.go deleted file mode 100644 index 8b3b5afd..00000000 --- a/internal/providersdk/resource_aggregate_route.go +++ /dev/null @@ -1,631 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strconv" - "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" -) - -type aggregateRouteOptions struct { - active bool - asPathAtomicAggregate bool - brief bool - discard bool - full bool - passive bool - metric int - preference int - asPathAggregatorAddress string - asPathAggregatorAsNumber string - asPathOrigin string - asPathPath string - destination string - routingInstance string - community []string - policy []string -} - -func resourceAggregateRoute() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceAggregateRouteCreate, - ReadWithoutTimeout: resourceAggregateRouteRead, - UpdateWithoutTimeout: resourceAggregateRouteUpdate, - DeleteWithoutTimeout: resourceAggregateRouteDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceAggregateRouteImport, - }, - Schema: map[string]*schema.Schema{ - "destination": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: junos.DefaultW, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "active": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"passive"}, - }, - "as_path_aggregator_address": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_as_number"}, - ValidateFunc: validation.IsIPAddress, - }, - "as_path_aggregator_as_number": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_address"}, - }, - "as_path_atomic_aggregate": { - Type: schema.TypeBool, - Optional: true, - }, - "as_path_origin": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"egp", "igp", "incomplete"}, false), - }, - "as_path_path": { - Type: schema.TypeString, - Optional: true, - }, - "brief": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"full"}, - }, - "community": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "discard": { - Type: schema.TypeBool, - Optional: true, - }, - "full": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"brief"}, - }, - "metric": { - Type: schema.TypeInt, - Optional: true, - }, - "passive": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"active"}, - }, - "policy": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - }, - }, - } -} - -func resourceAggregateRouteCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setAggregateRoute(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("destination").(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)))...) - } - } - aggregateRouteExists, err := checkAggregateRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if aggregateRouteExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("aggregate route %v already exists on table %s", - d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - if err := setAggregateRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_aggregate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - aggregateRouteExists, err = checkAggregateRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if aggregateRouteExists { - d.SetId(d.Get("destination").(string) + junos.IDSeparator + d.Get("routing_instance").(string)) - } else { - return append(diagWarns, - diag.FromErr(fmt.Errorf("aggregate route %v not exists in routing_instance %v after commit "+ - "=> check your config", d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - - return append(diagWarns, resourceAggregateRouteReadWJunSess(d, junSess)...) -} - -func resourceAggregateRouteRead(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 resourceAggregateRouteReadWJunSess(d, junSess) -} - -func resourceAggregateRouteReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - aggregateRouteOptions, err := readAggregateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if aggregateRouteOptions.destination == "" { - d.SetId("") - } else { - fillAggregateRouteData(d, aggregateRouteOptions) - } - - return nil -} - -func resourceAggregateRouteUpdate(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 := delAggregateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - return diag.FromErr(err) - } - if err := setAggregateRoute(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 := delAggregateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setAggregateRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_aggregate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceAggregateRouteReadWJunSess(d, junSess)...) -} - -func resourceAggregateRouteDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delAggregateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(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 := delAggregateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_aggregate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceAggregateRouteImport(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) - } - aggregateRouteExists, err := checkAggregateRouteExists(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - if !aggregateRouteExists { - return nil, fmt.Errorf("don't find aggregate route with id '%v' (id must be "+ - ""+junos.IDSeparator+")", d.Id()) - } - aggregateRouteOptions, err := readAggregateRoute(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - fillAggregateRouteData(d, aggregateRouteOptions) - - result[0] = d - - return result, nil -} - -func checkAggregateRouteExists(destination, instance string, junSess *junos.Session, -) (_ bool, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options aggregate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 aggregate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options aggregate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 aggregate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } - - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setAggregateRoute(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - var setPrefix string - if d.Get("routing_instance").(string) == junos.DefaultW { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = "set routing-options aggregate route " + d.Get("destination").(string) - } else { - setPrefix = "set routing-options rib inet6.0 aggregate route " + d.Get("destination").(string) - } - } else { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options aggregate route " + d.Get("destination").(string) - } else { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options rib " + d.Get("routing_instance").(string) + ".inet6.0 " + - "aggregate route " + d.Get("destination").(string) - } - } - configSet = append(configSet, setPrefix) - if d.Get("active").(bool) { - configSet = append(configSet, setPrefix+" active") - } - if d.Get("as_path_aggregator_address").(string) != "" && - d.Get("as_path_aggregator_as_number").(string) != "" { - configSet = append(configSet, setPrefix+" as-path aggregator "+ - d.Get("as_path_aggregator_as_number").(string)+" "+ - d.Get("as_path_aggregator_address").(string)) - } - if d.Get("as_path_atomic_aggregate").(bool) { - configSet = append(configSet, setPrefix+" as-path atomic-aggregate") - } - if v := d.Get("as_path_origin").(string); v != "" { - configSet = append(configSet, setPrefix+" as-path origin "+v) - } - if v := d.Get("as_path_path").(string); v != "" { - configSet = append(configSet, setPrefix+" as-path path \""+v+"\"") - } - if d.Get("brief").(bool) { - configSet = append(configSet, setPrefix+" brief") - } - for _, v := range d.Get("community").([]interface{}) { - configSet = append(configSet, setPrefix+" community "+v.(string)) - } - if d.Get("discard").(bool) { - configSet = append(configSet, setPrefix+" discard") - } - if d.Get("full").(bool) { - configSet = append(configSet, setPrefix+" full") - } - if d.Get("metric").(int) > 0 { - configSet = append(configSet, setPrefix+" metric "+strconv.Itoa(d.Get("metric").(int))) - } - if d.Get("passive").(bool) { - configSet = append(configSet, setPrefix+" passive") - } - for _, v := range d.Get("policy").([]interface{}) { - configSet = append(configSet, setPrefix+" policy "+v.(string)) - } - if d.Get("preference").(int) > 0 { - configSet = append(configSet, setPrefix+" preference "+strconv.Itoa(d.Get("preference").(int))) - } - - return junSess.ConfigSet(configSet) -} - -func readAggregateRoute(destination, instance string, junSess *junos.Session, -) (confRead aggregateRouteOptions, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options aggregate route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 aggregate route " + destination + junos.PipeDisplaySetRelative) - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options aggregate route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 aggregate route " + destination + junos.PipeDisplaySetRelative) - } - } - if err != nil { - return confRead, err - } - - if showConfig != junos.EmptyW { - confRead.destination = destination - 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 itemTrim == "active": - confRead.active = true - case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { //
- return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) - } - confRead.asPathAggregatorAsNumber = itemTrimFields[0] - confRead.asPathAggregatorAddress = itemTrimFields[1] - case itemTrim == "as-path atomic-aggregate": - confRead.asPathAtomicAggregate = true - case balt.CutPrefixInString(&itemTrim, "as-path origin "): - confRead.asPathOrigin = itemTrim - case balt.CutPrefixInString(&itemTrim, "as-path path "): - confRead.asPathPath = strings.Trim(itemTrim, "\"") - case itemTrim == "brief": - confRead.brief = true - case balt.CutPrefixInString(&itemTrim, "community "): - confRead.community = append(confRead.community, itemTrim) - case itemTrim == junos.DiscardW: - confRead.discard = true - case itemTrim == "full": - confRead.full = true - case balt.CutPrefixInString(&itemTrim, "metric "): - confRead.metric, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case itemTrim == "passive": - confRead.passive = true - case balt.CutPrefixInString(&itemTrim, "policy "): - confRead.policy = append(confRead.policy, itemTrim) - case balt.CutPrefixInString(&itemTrim, "preference "): - confRead.preference, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - } - } - - return confRead, nil -} - -func delAggregateRoute(destination, instance string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - configSet = append(configSet, "delete routing-options aggregate route "+destination) - } else { - configSet = append(configSet, "delete routing-options rib inet6.0 aggregate route "+destination) - } - } else { - if !strings.Contains(destination, ":") { - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options aggregate route "+destination) - } else { - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options rib "+instance+".inet6.0 aggregate route "+destination) - } - } - - return junSess.ConfigSet(configSet) -} - -func fillAggregateRouteData(d *schema.ResourceData, aggregateRouteOptions aggregateRouteOptions) { - if tfErr := d.Set("destination", aggregateRouteOptions.destination); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", aggregateRouteOptions.routingInstance); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("active", aggregateRouteOptions.active); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_address", aggregateRouteOptions.asPathAggregatorAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_as_number", aggregateRouteOptions.asPathAggregatorAsNumber); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_atomic_aggregate", aggregateRouteOptions.asPathAtomicAggregate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_origin", aggregateRouteOptions.asPathOrigin); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_path", aggregateRouteOptions.asPathPath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("brief", aggregateRouteOptions.brief); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("community", aggregateRouteOptions.community); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("discard", aggregateRouteOptions.discard); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("full", aggregateRouteOptions.full); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric", aggregateRouteOptions.metric); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("passive", aggregateRouteOptions.passive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("policy", aggregateRouteOptions.policy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("preference", aggregateRouteOptions.preference); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/providersdk/resource_group_dual_system.go b/internal/providersdk/resource_group_dual_system.go index e26d5fa8..0bf4e897 100644 --- a/internal/providersdk/resource_group_dual_system.go +++ b/internal/providersdk/resource_group_dual_system.go @@ -506,7 +506,7 @@ func setGroupDualSystem(d *schema.ResourceData, junSess *junos.Session) error { } staticRouteDestList = append(staticRouteDestList, staticRoute["destination"].(string)) for _, v3 := range staticRoute["next_hop"].([]interface{}) { - configSet = append(configSet, setPrefix+"routing-options static route "+ + configSet = append(configSet, setPrefix+junos.RoutingOptionsWS+"static route "+ staticRoute["destination"].(string)+" next-hop "+v3.(string)) } } @@ -611,7 +611,7 @@ func readGroupDualSystem(group string, junSess *junos.Session, confRead.interfaceFxp0[0]["family_inet6_address"] = append( confRead.interfaceFxp0[0]["family_inet6_address"].([]map[string]interface{}), familyInet6Address) } - case balt.CutPrefixInString(&itemTrim, "routing-options static route "): + case balt.CutPrefixInString(&itemTrim, junos.RoutingOptionsWS+"static route "): if len(confRead.routingOptions) == 0 { confRead.routingOptions = append(confRead.routingOptions, map[string]interface{}{ "static_route": make([]map[string]interface{}, 0), diff --git a/internal/providersdk/resource_rib_group.go b/internal/providersdk/resource_rib_group.go index 6e760c5a..a147fe5d 100644 --- a/internal/providersdk/resource_rib_group.go +++ b/internal/providersdk/resource_rib_group.go @@ -282,7 +282,8 @@ func resourceRibGroupImport(ctx context.Context, d *schema.ResourceData, m inter } func checkRibGroupExists(group string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + "routing-options rib-groups " + group + junos.PipeDisplaySet) + showConfig, err := junSess.Command(junos.CmdShowConfig + + junos.RoutingOptionsWS + "rib-groups " + group + junos.PipeDisplaySet) if err != nil { return false, err } @@ -313,7 +314,7 @@ func setRibGroup(d *schema.ResourceData, junSess *junos.Session) error { func readRibGroup(group string, junSess *junos.Session, ) (confRead ribGroupOptions, err error) { showConfig, err := junSess.Command(junos.CmdShowConfig + - "routing-options rib-groups " + group + junos.PipeDisplaySetRelative) + junos.RoutingOptionsWS + "rib-groups " + group + junos.PipeDisplaySetRelative) if err != nil { return confRead, err } From cd0cb25f1774080edfc6b62984664fc7edaab266 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 1 Sep 2023 09:27:16 +0200 Subject: [PATCH 38/48] r/generate_route: use new provider via framework --- .changes/route-with-fwk.md | 3 + docs/resources/generate_route.md | 8 +- internal/providerfwk/provider.go | 1 + .../providerfwk/resource_generate_route.go | 727 ++++++++++++++++++ .../resource_generate_route_test.go | 4 +- internal/providersdk/provider.go | 1 - .../providersdk/resource_generate_route.go | 648 ---------------- 7 files changed, 737 insertions(+), 655 deletions(-) create mode 100644 internal/providerfwk/resource_generate_route.go rename internal/{providersdk => providerfwk}/resource_generate_route_test.go (98%) delete mode 100644 internal/providersdk/resource_generate_route.go diff --git a/.changes/route-with-fwk.md b/.changes/route-with-fwk.md index 8627a60d..b4959dec 100644 --- a/.changes/route-with-fwk.md +++ b/.changes/route-with-fwk.md @@ -4,3 +4,6 @@ ENHANCEMENTS: * **resource/junos_aggregate_route**: 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 +* **resource/junos_generate_route**: 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/generate_route.md b/docs/resources/generate_route.md index b4fcc83e..35ed31e2 100644 --- a/docs/resources/generate_route.md +++ b/docs/resources/generate_route.md @@ -22,9 +22,9 @@ resource "junos_generate_route" "demo_generate_route" { The following arguments are supported: - **destination** (Required, String, Forces new resource) - The destination for generate route. + Destination prefix. - **routing_instance** (Optional, String, Forces new resource) - Routing instance for route. + Routing instance for generate route. Need to be `default` or name of routing instance. Defaults to `default`. - **active** (Optional, Boolean) @@ -43,7 +43,7 @@ The following arguments are supported: - **brief** (Optional, Boolean) Include longest common sequences from contributing paths. - **community** (Optional, List of String) - List of BGP community. + BGP community. - **discard** (Optional, Boolean) Drop packets to destination; send no ICMP unreachables. - **full** (Optional, Boolean) @@ -56,7 +56,7 @@ The following arguments are supported: - **passive** (Optional, Boolean) Retain inactive route in forwarding table. - **policy** (Optional, List of String) - List of Policy filter. + Policy filter. - **preference** (Optional, Number) Preference for generate route. diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 4ace52b8..b7047604 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -208,6 +208,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newFirewallPolicerResource, newForwardingoptionsSamplingResource, newForwardingoptionsSamplingInstanceResource, + newGenerateRouteResource, newInterfaceLogicalResource, newInterfacePhysicalDisableResource, newInterfacePhysicalResource, diff --git a/internal/providerfwk/resource_generate_route.go b/internal/providerfwk/resource_generate_route.go new file mode 100644 index 00000000..249b7ce6 --- /dev/null +++ b/internal/providerfwk/resource_generate_route.go @@ -0,0 +1,727 @@ +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/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 = &generateRoute{} + _ resource.ResourceWithConfigure = &generateRoute{} + _ resource.ResourceWithValidateConfig = &generateRoute{} + _ resource.ResourceWithImportState = &generateRoute{} +) + +type generateRoute struct { + client *junos.Client +} + +func newGenerateRouteResource() resource.Resource { + return &generateRoute{} +} + +func (rsc *generateRoute) typeName() string { + return providerName + "_generate_route" +} + +func (rsc *generateRoute) junosName() string { + return "generate route" +} + +func (rsc *generateRoute) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *generateRoute) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *generateRoute) 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 *generateRoute) 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 " + + "`" + junos.IDSeparator + "`.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "destination": schema.StringAttribute{ + Required: true, + Description: "Destination prefix.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(junos.DefaultW), + Description: "Routing instance for generate route.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "active": schema.BoolAttribute{ + Optional: true, + Description: "Remove inactive route from forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_aggregator_address": schema.StringAttribute{ + Optional: true, + Description: "Address of BGP system to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "as_path_aggregator_as_number": schema.StringAttribute{ + Optional: true, + Description: "AS number to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "as_path_atomic_aggregate": schema.BoolAttribute{ + Optional: true, + Description: "Add ATOMIC_AGGREGATE path attribute to route.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_origin": schema.StringAttribute{ + Optional: true, + Description: "Define origin.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + "as_path_path": schema.StringAttribute{ + Optional: true, + Description: "Path to as-path.", + Validators: []validator.String{ + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "brief": schema.BoolAttribute{ + Optional: true, + Description: "Include longest common sequences from contributing paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "community": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "BGP community.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "discard": schema.BoolAttribute{ + Optional: true, + Description: "Drop packets to destination; send no ICMP unreachables.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "full": schema.BoolAttribute{ + Optional: true, + Description: "Include all AS numbers from all contributing paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric for generate route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "next_table": schema.StringAttribute{ + Optional: true, + Description: "Next hop to another table.", + Validators: []validator.String{ + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "passive": schema.BoolAttribute{ + Optional: true, + Description: "Retain inactive route in forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "policy": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Policy filter.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference for aggregate route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + } +} + +type generateRouteData struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Brief types.Bool `tfsdk:"brief"` + Discard types.Bool `tfsdk:"discard"` + Full types.Bool `tfsdk:"full"` + Passive types.Bool `tfsdk:"passive"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community []types.String `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + NextTable types.String `tfsdk:"next_table"` + Policy []types.String `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` +} + +type generateRouteConfig struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Brief types.Bool `tfsdk:"brief"` + Discard types.Bool `tfsdk:"discard"` + Full types.Bool `tfsdk:"full"` + Passive types.Bool `tfsdk:"passive"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community types.List `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + NextTable types.String `tfsdk:"next_table"` + Policy types.List `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` +} + +func (rsc *generateRoute) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config generateRouteConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.Active.IsNull() && + !config.Passive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("active"), + tfdiag.ConflictConfigErrSummary, + "active and passive cannot be configured together", + ) + } + if !config.ASPathAggregatorASNumber.IsNull() && + config.ASPathAggregatorAddress.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_as_number"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_address must be specified with as_path_aggregator_as_number", + ) + } + if !config.ASPathAggregatorAddress.IsNull() && + config.ASPathAggregatorASNumber.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_address"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_as_number must be specified with as_path_aggregator_address", + ) + } + if !config.Brief.IsNull() && + !config.Full.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("brief"), + tfdiag.ConflictConfigErrSummary, + "brief and full cannot be configured together", + ) + } + if !config.Discard.IsNull() && + !config.NextTable.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and next_table cannot be configured together", + ) + } +} + +func (rsc *generateRoute) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan generateRouteData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Destination.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("destination"), + "Empty Destination", + "could not create "+rsc.junosName()+" with empty destination", + ) + + return + } + + 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 + } + } + routeExists, err := checkGenerateRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + routeExists, err := checkGenerateRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *generateRoute) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data generateRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom2String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Destination.ValueString(), + state.RoutingInstance.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *generateRoute) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state generateRouteData + 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 *generateRoute) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state generateRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *generateRoute) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data generateRouteData + + 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 checkGenerateRouteExists( + _ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + _ bool, err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "generate route " + destination + junos.PipeDisplaySet) + if err != nil { + return false, err + } + + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *generateRouteData) fillID() { + if v := rscData.RoutingInstance.ValueString(); v != "" { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + v) + } else { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + junos.DefaultW) + } +} + +func (rscData *generateRouteData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *generateRouteData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := junos.SetLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + setPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += junos.RibInet60WS + } + default: + setPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + setPrefix += "generate route " + rscData.Destination.ValueString() + " " + configSet := []string{ + setPrefix, + } + + if rscData.Active.ValueBool() { + configSet = append(configSet, setPrefix+"active") + } + if vNumber, vAddress := rscData.ASPathAggregatorASNumber.ValueString(), + rscData.ASPathAggregatorAddress.ValueString(); vNumber != "" && vAddress != "" { + configSet = append(configSet, setPrefix+"as-path aggregator "+vNumber+" "+vAddress) + } + if rscData.ASPathAtomicAggregate.ValueBool() { + configSet = append(configSet, setPrefix+"as-path atomic-aggregate") + } + if v := rscData.ASPathOrigin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path origin "+v) + } + if v := rscData.ASPathPath.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path path \""+v+"\"") + } + if rscData.Brief.ValueBool() { + configSet = append(configSet, setPrefix+"brief") + } + for _, v := range rscData.Community { + configSet = append(configSet, setPrefix+"community \""+v.ValueString()+"\"") + } + if rscData.Discard.ValueBool() { + configSet = append(configSet, setPrefix+"discard") + } + if rscData.Full.ValueBool() { + configSet = append(configSet, setPrefix+"full") + } + if !rscData.Metric.IsNull() { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(rscData.Metric.ValueInt64())) + } + if v := rscData.NextTable.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"next-table \""+v+"\"") + } + if rscData.Passive.ValueBool() { + configSet = append(configSet, setPrefix+"passive") + } + for _, v := range rscData.Policy { + configSet = append(configSet, setPrefix+"policy \""+v.ValueString()+"\"") + } + if !rscData.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(rscData.Preference.ValueInt64())) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *generateRouteData) read( + _ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "generate route " + destination + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + + if showConfig != junos.EmptyW { + rscData.Destination = types.StringValue(destination) + 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 itemTrim == "active": + rscData.Active = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { //
+ return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) + } + rscData.ASPathAggregatorASNumber = types.StringValue(itemTrimFields[0]) + rscData.ASPathAggregatorAddress = types.StringValue(itemTrimFields[1]) + case itemTrim == "as-path atomic-aggregate": + rscData.ASPathAtomicAggregate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path origin "): + rscData.ASPathOrigin = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "as-path path "): + rscData.ASPathPath = types.StringValue(strings.Trim(itemTrim, "\"")) + case itemTrim == "brief": + rscData.Brief = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "community "): + rscData.Community = append(rscData.Community, types.StringValue(strings.Trim(itemTrim, "\""))) + case itemTrim == junos.DiscardW: + rscData.Discard = types.BoolValue(true) + case itemTrim == "full": + rscData.Full = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "metric "): + rscData.Metric, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "next-table "): + rscData.NextTable = types.StringValue(strings.Trim(itemTrim, "\"")) + case itemTrim == "passive": + rscData.Passive = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "policy "): + rscData.Policy = append(rscData.Policy, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "preference "): + rscData.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + } + } + + return nil +} + +func (rscData *generateRouteData) del( + _ context.Context, junSess *junos.Session, +) error { + delPrefix := junos.DeleteLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + delPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += junos.RibInet60WS + } + default: + delPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + configSet := []string{ + delPrefix + "generate route " + rscData.Destination.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_generate_route_test.go b/internal/providerfwk/resource_generate_route_test.go similarity index 98% rename from internal/providersdk/resource_generate_route_test.go rename to internal/providerfwk/resource_generate_route_test.go index e8bdbb3f..7014148b 100644 --- a/internal/providersdk/resource_generate_route_test.go +++ b/internal/providerfwk/resource_generate_route_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 TestAccJunosGenerateRoute_basic(t *testing.T) { diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 483d482c..fd42441f 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -156,7 +156,6 @@ func Provider() *schema.Provider { "junos_forwardingoptions_dhcprelay": resourceForwardingOptionsDhcpRelay(), "junos_forwardingoptions_dhcprelay_group": resourceForwardingOptionsDhcpRelayGroup(), "junos_forwardingoptions_dhcprelay_servergroup": resourceForwardingOptionsDhcpRelayServerGroup(), - "junos_generate_route": resourceGenerateRoute(), "junos_group_dual_system": resourceGroupDualSystem(), "junos_igmp_snooping_vlan": resourceIgmpSnoopingVlan(), "junos_layer2_control": resourceLayer2Control(), diff --git a/internal/providersdk/resource_generate_route.go b/internal/providersdk/resource_generate_route.go deleted file mode 100644 index c10d9e0c..00000000 --- a/internal/providersdk/resource_generate_route.go +++ /dev/null @@ -1,648 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strconv" - "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" -) - -type generateRouteOptions struct { - active bool - asPathAtomicAggregate bool - brief bool - discard bool - full bool - passive bool - metric int - preference int - asPathAggregatorAddress string - asPathAggregatorAsNumber string - asPathOrigin string - asPathPath string - destination string - nextTable string - routingInstance string - community []string - policy []string -} - -func resourceGenerateRoute() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceGenerateRouteCreate, - ReadWithoutTimeout: resourceGenerateRouteRead, - UpdateWithoutTimeout: resourceGenerateRouteUpdate, - DeleteWithoutTimeout: resourceGenerateRouteDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceGenerateRouteImport, - }, - Schema: map[string]*schema.Schema{ - "destination": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: junos.DefaultW, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "active": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"passive"}, - }, - "as_path_aggregator_address": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_as_number"}, - ValidateFunc: validation.IsIPAddress, - }, - "as_path_aggregator_as_number": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_address"}, - }, - "as_path_atomic_aggregate": { - Type: schema.TypeBool, - Optional: true, - }, - "as_path_origin": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"egp", "igp", "incomplete"}, false), - }, - "as_path_path": { - Type: schema.TypeString, - Optional: true, - }, - "brief": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"full"}, - }, - "community": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "discard": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"next_table"}, - }, - "full": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"brief"}, - }, - "metric": { - Type: schema.TypeInt, - Optional: true, - }, - "next_table": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"discard"}, - }, - "passive": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"active"}, - }, - "policy": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - }, - }, - } -} - -func resourceGenerateRouteCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setGenerateRoute(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("destination").(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)))...) - } - } - generateRouteExists, err := checkGenerateRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if generateRouteExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("generate route %v already exists on table %s", - d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - if err := setGenerateRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_generate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - generateRouteExists, err = checkGenerateRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if generateRouteExists { - d.SetId(d.Get("destination").(string) + junos.IDSeparator + d.Get("routing_instance").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("generate route %v not exists in routing_instance %v after commit "+ - "=> check your config", d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - - return append(diagWarns, resourceGenerateRouteReadWJunSess(d, junSess)...) -} - -func resourceGenerateRouteRead(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 resourceGenerateRouteReadWJunSess(d, junSess) -} - -func resourceGenerateRouteReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - generateRouteOptions, err := readGenerateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if generateRouteOptions.destination == "" { - d.SetId("") - } else { - fillGenerateRouteData(d, generateRouteOptions) - } - - return nil -} - -func resourceGenerateRouteUpdate(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 := delGenerateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - return diag.FromErr(err) - } - if err := setGenerateRoute(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 := delGenerateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setGenerateRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_generate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - d.Partial(false) - - return append(diagWarns, resourceGenerateRouteReadWJunSess(d, junSess)...) -} - -func resourceGenerateRouteDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delGenerateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(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 := delGenerateRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_generate_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceGenerateRouteImport(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) - } - generateRouteExists, err := checkGenerateRouteExists(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - if !generateRouteExists { - return nil, fmt.Errorf("don't find generate route with id '%v' (id must be "+ - ""+junos.IDSeparator+")", d.Id()) - } - generateRouteOptions, err := readGenerateRoute(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - fillGenerateRouteData(d, generateRouteOptions) - - result[0] = d - - return result, nil -} - -func checkGenerateRouteExists(destination, instance string, junSess *junos.Session, -) (_ bool, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options generate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 " + "generate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options generate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 generate route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } - - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setGenerateRoute(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - var setPrefix string - if d.Get("routing_instance").(string) == junos.DefaultW { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = "set routing-options generate route " + d.Get("destination").(string) + " " - } else { - setPrefix = "set routing-options rib inet6.0 generate route " + d.Get("destination").(string) + " " - } - } else { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options generate route " + d.Get("destination").(string) + " " - } else { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options rib " + d.Get("routing_instance").(string) + ".inet6.0 " + - "generate route " + d.Get("destination").(string) + " " - } - } - if d.Get("active").(bool) { - configSet = append(configSet, setPrefix+"active") - } - if d.Get("as_path_aggregator_address").(string) != "" && - d.Get("as_path_aggregator_as_number").(string) != "" { - configSet = append(configSet, setPrefix+"as-path aggregator "+ - d.Get("as_path_aggregator_as_number").(string)+" "+ - d.Get("as_path_aggregator_address").(string)) - } - if d.Get("as_path_atomic_aggregate").(bool) { - configSet = append(configSet, setPrefix+"as-path atomic-aggregate") - } - if v := d.Get("as_path_origin").(string); v != "" { - configSet = append(configSet, setPrefix+"as-path origin "+v) - } - if v := d.Get("as_path_path").(string); v != "" { - configSet = append(configSet, setPrefix+"as-path path \""+v+"\"") - } - if d.Get("brief").(bool) { - configSet = append(configSet, setPrefix+"brief") - } - for _, v := range d.Get("community").([]interface{}) { - configSet = append(configSet, setPrefix+"community "+v.(string)) - } - if d.Get("discard").(bool) { - configSet = append(configSet, setPrefix+"discard") - } - if d.Get("full").(bool) { - configSet = append(configSet, setPrefix+"full") - } - if d.Get("metric").(int) > 0 { - configSet = append(configSet, setPrefix+"metric "+strconv.Itoa(d.Get("metric").(int))) - } - if d.Get("next_table").(string) != "" { - configSet = append(configSet, setPrefix+"next-table "+d.Get("next_table").(string)) - } - if d.Get("passive").(bool) { - configSet = append(configSet, setPrefix+"passive") - } - for _, v := range d.Get("policy").([]interface{}) { - configSet = append(configSet, setPrefix+"policy "+v.(string)) - } - if d.Get("preference").(int) > 0 { - configSet = append(configSet, setPrefix+"preference "+strconv.Itoa(d.Get("preference").(int))) - } - - return junSess.ConfigSet(configSet) -} - -func readGenerateRoute(destination, instance string, junSess *junos.Session, -) (confRead generateRouteOptions, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options generate route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 generate route " + destination + junos.PipeDisplaySetRelative) - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options generate route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 generate route " + destination + junos.PipeDisplaySetRelative) - } - } - if err != nil { - return confRead, err - } - - if showConfig != junos.EmptyW { - confRead.destination = destination - 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 itemTrim == "active": - confRead.active = true - case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { //
- return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) - } - confRead.asPathAggregatorAsNumber = itemTrimFields[0] - confRead.asPathAggregatorAddress = itemTrimFields[1] - case itemTrim == "as-path atomic-aggregate": - confRead.asPathAtomicAggregate = true - case balt.CutPrefixInString(&itemTrim, "as-path origin "): - confRead.asPathOrigin = itemTrim - case balt.CutPrefixInString(&itemTrim, "as-path path "): - confRead.asPathPath = strings.Trim(itemTrim, "\"") - case itemTrim == "brief": - confRead.brief = true - case balt.CutPrefixInString(&itemTrim, "community "): - confRead.community = append(confRead.community, itemTrim) - case itemTrim == junos.DiscardW: - confRead.discard = true - case itemTrim == "full": - confRead.full = true - case balt.CutPrefixInString(&itemTrim, "metric "): - confRead.metric, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "next-table "): - confRead.nextTable = itemTrim - case itemTrim == "passive": - confRead.passive = true - case balt.CutPrefixInString(&itemTrim, "policy "): - confRead.policy = append(confRead.policy, itemTrim) - case balt.CutPrefixInString(&itemTrim, "preference "): - confRead.preference, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - } - } - - return confRead, nil -} - -func delGenerateRoute(destination, instance string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - configSet = append(configSet, "delete routing-options generate route "+destination) - } else { - configSet = append(configSet, "delete routing-options rib inet6.0 generate route "+destination) - } - } else { - if !strings.Contains(destination, ":") { - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options generate route "+destination) - } else { - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options rib "+instance+".inet6.0 generate route "+destination) - } - } - - return junSess.ConfigSet(configSet) -} - -func fillGenerateRouteData(d *schema.ResourceData, generateRouteOptions generateRouteOptions) { - if tfErr := d.Set("destination", generateRouteOptions.destination); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", generateRouteOptions.routingInstance); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("active", generateRouteOptions.active); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_address", generateRouteOptions.asPathAggregatorAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_as_number", generateRouteOptions.asPathAggregatorAsNumber); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_atomic_aggregate", generateRouteOptions.asPathAtomicAggregate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_origin", generateRouteOptions.asPathOrigin); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_path", generateRouteOptions.asPathPath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("brief", generateRouteOptions.brief); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("community", generateRouteOptions.community); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("discard", generateRouteOptions.discard); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("discard", generateRouteOptions.discard); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("full", generateRouteOptions.full); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric", generateRouteOptions.metric); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("next_table", generateRouteOptions.nextTable); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("passive", generateRouteOptions.passive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("policy", generateRouteOptions.policy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("preference", generateRouteOptions.preference); tfErr != nil { - panic(tfErr) - } -} From e6450acb3dbf4bdd8afa42bb75439395505393c1 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 1 Sep 2023 14:05:22 +0200 Subject: [PATCH 39/48] r/static_route: use new provider via framework --- .changes/route-with-fwk.md | 4 + docs/resources/static_route.md | 12 +- internal/providerfwk/provider.go | 1 + internal/providerfwk/resource_static_route.go | 1103 +++++++++++++++++ .../resource_static_route_test.go | 12 +- internal/providersdk/provider.go | 1 - internal/providersdk/resource_static_route.go | 824 ------------ 7 files changed, 1123 insertions(+), 834 deletions(-) create mode 100644 internal/providerfwk/resource_static_route.go rename internal/{providersdk => providerfwk}/resource_static_route_test.go (98%) delete mode 100644 internal/providersdk/resource_static_route.go diff --git a/.changes/route-with-fwk.md b/.changes/route-with-fwk.md index b4959dec..a78c3b0e 100644 --- a/.changes/route-with-fwk.md +++ b/.changes/route-with-fwk.md @@ -7,3 +7,7 @@ ENHANCEMENTS: * **resource/junos_generate_route**: 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 +* **resource/junos_static_route**: 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 diff --git a/docs/resources/static_route.md b/docs/resources/static_route.md index f653d56f..00d98a7b 100644 --- a/docs/resources/static_route.md +++ b/docs/resources/static_route.md @@ -22,9 +22,9 @@ resource "junos_static_route" "demo_static_route" { The following arguments are supported: - **destination** (Required, String, Forces new resource) - The destination for static route. + Destination prefix. - **routing_instance** (Optional, String, Forces new resource) - Routing instance for route. + Routing instance for static route. Need to be `default` or name of routing instance. Defaults to `default`. - **active** (Optional, Boolean) @@ -42,7 +42,7 @@ The following arguments are supported: - **as_path_path** (Optional, String) Path to as-path. - **community** (Optional, List of String) - List of BGP community. + BGP community. - **discard** (Optional, Boolean) Drop packets to destination; send no ICMP unreachables. Conflict with `next_hop`, `next_table`, `qualified_next_hop`, `receive` and `reject`. @@ -55,7 +55,7 @@ The following arguments are supported: - **metric** (Optional, Number) Metric for static route. - **next_hop** (Optional, List of String) - List of next-hop. + Next-hop to destination. Conflict with `discard`, `next_table`, `receive` and `reject`. - **next_table** (Optional, String) Next hop to another table. @@ -66,10 +66,10 @@ The following arguments are supported: - **preference** (Optional, Number) Preference for static route. - **qualified_next_hop** (Optional, Block List) - For each `next_hop`. + For each `next_hop` with qualifiers. Conflict with `discard`, `next_table`, `receive` and `reject`. - **next_hop** (Required, String) - Target for qualified-next-hop. + Next-hop with qualifiers to destination. - **interface** (Optional, String) Interface of qualified next hop (Cannot be used with interface set as next-hop). - **metric** (Optional, Number) diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index b7047604..4220568c 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -242,6 +242,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newSecurityZoneBookAddressSetResource, newServicesFlowMonitoringV9TemplateResource, newServicesFlowMonitoringVIPFixTemplateResource, + newStaticRouteResource, } } diff --git a/internal/providerfwk/resource_static_route.go b/internal/providerfwk/resource_static_route.go new file mode 100644 index 00000000..e603beb0 --- /dev/null +++ b/internal/providerfwk/resource_static_route.go @@ -0,0 +1,1103 @@ +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/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 = &staticRoute{} + _ resource.ResourceWithConfigure = &staticRoute{} + _ resource.ResourceWithValidateConfig = &staticRoute{} + _ resource.ResourceWithImportState = &staticRoute{} +) + +type staticRoute struct { + client *junos.Client +} + +func newStaticRouteResource() resource.Resource { + return &staticRoute{} +} + +func (rsc *staticRoute) typeName() string { + return providerName + "_static_route" +} + +func (rsc *staticRoute) junosName() string { + return "static route" +} + +func (rsc *staticRoute) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *staticRoute) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *staticRoute) 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 *staticRoute) 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 " + + "`" + junos.IDSeparator + "`.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "destination": schema.StringAttribute{ + Required: true, + Description: "Destination prefix.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(junos.DefaultW), + Description: "Routing instance for static route.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "active": schema.BoolAttribute{ + Optional: true, + Description: "Remove inactive route from forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_aggregator_address": schema.StringAttribute{ + Optional: true, + Description: "Address of BGP system to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "as_path_aggregator_as_number": schema.StringAttribute{ + Optional: true, + Description: "AS number to add AGGREGATOR path attribute to route.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "as_path_atomic_aggregate": schema.BoolAttribute{ + Optional: true, + Description: "Add ATOMIC_AGGREGATE path attribute to route.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_path_origin": schema.StringAttribute{ + Optional: true, + Description: "Define origin.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + "as_path_path": schema.StringAttribute{ + Optional: true, + Description: "Path to as-path.", + Validators: []validator.String{ + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "community": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "BGP community.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "discard": schema.BoolAttribute{ + Optional: true, + Description: "Drop packets to destination; send no ICMP unreachables.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "install": schema.BoolAttribute{ + Optional: true, + Description: "Install route into forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_install": schema.BoolAttribute{ + Optional: true, + Description: "Don't install route into forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric for static route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "next_hop": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Next-hop to destination.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringIPAddress(), + ), + ), + }, + }, + "next_table": schema.StringAttribute{ + Optional: true, + Description: "Next hop to another table.", + Validators: []validator.String{ + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "passive": schema.BoolAttribute{ + Optional: true, + Description: "Retain inactive route in forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference for aggregate route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "readvertise": schema.BoolAttribute{ + Optional: true, + Description: "Mark route as eligible to be readvertised.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_readvertise": schema.BoolAttribute{ + Optional: true, + Description: "Don't mark route as eligible to be readvertised.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "receive": schema.BoolAttribute{ + Optional: true, + Description: "Install a receive route for the destination.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "reject": schema.BoolAttribute{ + Optional: true, + Description: "Drop packets to destination; send ICMP unreachables.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "resolve": schema.BoolAttribute{ + Optional: true, + Description: "Allow resolution of indirectly connected next hops.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_resolve": schema.BoolAttribute{ + Optional: true, + Description: "Don't allow resolution of indirectly connected next hops.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "retain": schema.BoolAttribute{ + Optional: true, + Description: "Always keep route in forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_retain": schema.BoolAttribute{ + Optional: true, + Description: "Don't always keep route in forwarding table.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "qualified_next_hop": schema.ListNestedBlock{ + Description: "For each `next_hop` with qualifiers.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "next_hop": schema.StringAttribute{ + Required: true, + Description: "Next-hop with qualifiers to destination.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringIPAddress(), + ), + }, + }, + "interface": schema.StringAttribute{ + Optional: true, + Description: "Interface of qualified next hop.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric of qualified next hop.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference of qualified next hop.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + }, + }, + }, + } +} + +type staticRouteData struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Discard types.Bool `tfsdk:"discard"` + Install types.Bool `tfsdk:"install"` + NoInstall types.Bool `tfsdk:"no_install"` + Passive types.Bool `tfsdk:"passive"` + Readvertise types.Bool `tfsdk:"readvertise"` + NoReadvertise types.Bool `tfsdk:"no_readvertise"` + Receive types.Bool `tfsdk:"receive"` + Reject types.Bool `tfsdk:"reject"` + Resolve types.Bool `tfsdk:"resolve"` + NoResolve types.Bool `tfsdk:"no_resolve"` + Retain types.Bool `tfsdk:"retain"` + NoRetain types.Bool `tfsdk:"no_retain"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community []types.String `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + NextHop []types.String `tfsdk:"next_hop"` + NextTable types.String `tfsdk:"next_table"` + Preference types.Int64 `tfsdk:"preference"` + QualifiedNextHop []staticRouteBlockQualifiedNextHop `tfsdk:"qualified_next_hop"` +} + +type staticRouteConfig struct { + Active types.Bool `tfsdk:"active"` + ASPathAtomicAggregate types.Bool `tfsdk:"as_path_atomic_aggregate"` + Discard types.Bool `tfsdk:"discard"` + Install types.Bool `tfsdk:"install"` + NoInstall types.Bool `tfsdk:"no_install"` + Passive types.Bool `tfsdk:"passive"` + Readvertise types.Bool `tfsdk:"readvertise"` + NoReadvertise types.Bool `tfsdk:"no_readvertise"` + Receive types.Bool `tfsdk:"receive"` + Reject types.Bool `tfsdk:"reject"` + Resolve types.Bool `tfsdk:"resolve"` + NoResolve types.Bool `tfsdk:"no_resolve"` + Retain types.Bool `tfsdk:"retain"` + NoRetain types.Bool `tfsdk:"no_retain"` + ID types.String `tfsdk:"id"` + Destination types.String `tfsdk:"destination"` + RoutingInstance types.String `tfsdk:"routing_instance"` + ASPathAggregatorAddress types.String `tfsdk:"as_path_aggregator_address"` + ASPathAggregatorASNumber types.String `tfsdk:"as_path_aggregator_as_number"` + ASPathOrigin types.String `tfsdk:"as_path_origin"` + ASPathPath types.String `tfsdk:"as_path_path"` + Community types.List `tfsdk:"community"` + Metric types.Int64 `tfsdk:"metric"` + NextHop types.List `tfsdk:"next_hop"` + NextTable types.String `tfsdk:"next_table"` + Preference types.Int64 `tfsdk:"preference"` + QualifiedNextHop types.List `tfsdk:"qualified_next_hop"` +} + +type staticRouteBlockQualifiedNextHop struct { + NextHop types.String `tfsdk:"next_hop"` + Interface types.String `tfsdk:"interface"` + Metric types.Int64 `tfsdk:"metric"` + Preference types.Int64 `tfsdk:"preference"` +} + +func (rsc *staticRoute) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config staticRouteConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.Active.IsNull() && + !config.Passive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("active"), + tfdiag.ConflictConfigErrSummary, + "active and passive cannot be configured together", + ) + } + if !config.ASPathAggregatorASNumber.IsNull() && + config.ASPathAggregatorAddress.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_as_number"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_address must be specified with as_path_aggregator_as_number", + ) + } + if !config.ASPathAggregatorAddress.IsNull() && + config.ASPathAggregatorASNumber.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("as_path_aggregator_address"), + tfdiag.MissingConfigErrSummary, + "as_path_aggregator_as_number must be specified with as_path_aggregator_address", + ) + } + if !config.Discard.IsNull() { + if !config.NextHop.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and next_hop cannot be configured together", + ) + } + if !config.NextTable.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and next_table cannot be configured together", + ) + } + if !config.QualifiedNextHop.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and qualified_next_hop cannot be configured together", + ) + } + if !config.Receive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and receive cannot be configured together", + ) + } + if !config.Reject.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("discard"), + tfdiag.ConflictConfigErrSummary, + "discard and reject cannot be configured together", + ) + } + } + if !config.Install.IsNull() && + !config.NoInstall.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("install"), + tfdiag.ConflictConfigErrSummary, + "install and no_install cannot be configured together", + ) + } + if !config.NextHop.IsNull() { + if !config.NextTable.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_hop"), + tfdiag.ConflictConfigErrSummary, + "next_hop and next_table cannot be configured together", + ) + } + if !config.Receive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_hop"), + tfdiag.ConflictConfigErrSummary, + "next_hop and receive cannot be configured together", + ) + } + if !config.Reject.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_hop"), + tfdiag.ConflictConfigErrSummary, + "next_hop and reject cannot be configured together", + ) + } + } + if !config.NextTable.IsNull() { + if !config.QualifiedNextHop.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_table"), + tfdiag.ConflictConfigErrSummary, + "next_table and qualified_next_hop cannot be configured together", + ) + } + if !config.Receive.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_table"), + tfdiag.ConflictConfigErrSummary, + "next_table and receive cannot be configured together", + ) + } + if !config.Reject.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("next_hop"), + tfdiag.ConflictConfigErrSummary, + "next_table and reject cannot be configured together", + ) + } + } + if !config.Readvertise.IsNull() && + !config.NoReadvertise.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("readvertise"), + tfdiag.ConflictConfigErrSummary, + "readvertise and no_readvertise cannot be configured together", + ) + } + if !config.Receive.IsNull() { + if !config.QualifiedNextHop.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("receive"), + tfdiag.ConflictConfigErrSummary, + "receive and qualified_next_hop cannot be configured together", + ) + } + if !config.Reject.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("receive"), + tfdiag.ConflictConfigErrSummary, + "receive and reject cannot be configured together", + ) + } + } + if !config.Reject.IsNull() { + if !config.QualifiedNextHop.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("reject"), + tfdiag.ConflictConfigErrSummary, + "reject and qualified_next_hop cannot be configured together", + ) + } + } + if !config.Resolve.IsNull() { + if !config.NoResolve.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("resolve"), + tfdiag.ConflictConfigErrSummary, + "resolve and no_resolve cannot be configured together", + ) + } + if !config.Retain.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("resolve"), + tfdiag.ConflictConfigErrSummary, + "resolve and retain cannot be configured together", + ) + } + if !config.NoRetain.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("resolve"), + tfdiag.ConflictConfigErrSummary, + "resolve and no_retain cannot be configured together", + ) + } + } + if !config.Retain.IsNull() && + !config.NoRetain.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("retain"), + tfdiag.ConflictConfigErrSummary, + "retain and no_retain cannot be configured together", + ) + } + if !config.QualifiedNextHop.IsNull() && !config.QualifiedNextHop.IsUnknown() { + var configQualifiedNextHop []staticRouteBlockQualifiedNextHop + asDiags := config.QualifiedNextHop.ElementsAs(ctx, &configQualifiedNextHop, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + qualifiedNextHopNextHop := make(map[string]struct{}) + for i, block := range configQualifiedNextHop { + if block.NextHop.IsUnknown() { + continue + } + if _, ok := qualifiedNextHopNextHop[block.NextHop.ValueString()]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("qualified_next_hop").AtListIndex(i).AtName("next_hop"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple qualified_next_hop blocks with the same next_hop %q", + block.NextHop.ValueString()), + ) + } + qualifiedNextHopNextHop[block.NextHop.ValueString()] = struct{}{} + } + } +} + +func (rsc *staticRoute) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan staticRouteData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Destination.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("destination"), + "Empty Destination", + "could not create "+rsc.junosName()+" with empty destination", + ) + + return + } + + 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 + } + } + routeExists, err := checkStaticRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + routeExists, err := checkStaticRouteExists( + fnCtx, + plan.Destination.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !routeExists { + 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.Destination.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Destination.ValueString()), + ) + } + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *staticRoute) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data staticRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom2String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Destination.ValueString(), + state.RoutingInstance.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *staticRoute) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state staticRouteData + 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 *staticRoute) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state staticRouteData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *staticRoute) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data staticRouteData + + 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 checkStaticRouteExists(_ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + _ bool, err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "static route " + destination + junos.PipeDisplaySet) + if err != nil { + return false, err + } + + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *staticRouteData) fillID() { + if v := rscData.RoutingInstance.ValueString(); v != "" { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + v) + } else { + rscData.ID = types.StringValue(rscData.Destination.ValueString() + junos.IDSeparator + junos.DefaultW) + } +} + +func (rscData *staticRouteData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *staticRouteData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := junos.SetLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + setPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += junos.RibInet60WS + } + default: + setPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + setPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + setPrefix += "static route " + rscData.Destination.ValueString() + " " + + if rscData.Active.ValueBool() { + configSet = append(configSet, setPrefix+"active") + } + if vNumber, vAddress := rscData.ASPathAggregatorASNumber.ValueString(), + rscData.ASPathAggregatorAddress.ValueString(); vNumber != "" && vAddress != "" { + configSet = append(configSet, setPrefix+"as-path aggregator "+vNumber+" "+vAddress) + } + if rscData.ASPathAtomicAggregate.ValueBool() { + configSet = append(configSet, setPrefix+"as-path atomic-aggregate") + } + if v := rscData.ASPathOrigin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path origin "+v) + } + if v := rscData.ASPathPath.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path path \""+v+"\"") + } + for _, v := range rscData.Community { + configSet = append(configSet, setPrefix+"community \""+v.ValueString()+"\"") + } + if rscData.Discard.ValueBool() { + configSet = append(configSet, setPrefix+"discard") + } + if rscData.Install.ValueBool() { + configSet = append(configSet, setPrefix+"install") + } + if rscData.NoInstall.ValueBool() { + configSet = append(configSet, setPrefix+"no-install") + } + if !rscData.Metric.IsNull() { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(rscData.Metric.ValueInt64())) + } + for _, v := range rscData.NextHop { + configSet = append(configSet, setPrefix+"next-hop "+v.ValueString()) + } + if v := rscData.NextTable.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"next-table \""+v+"\"") + } + if rscData.Passive.ValueBool() { + configSet = append(configSet, setPrefix+"passive") + } + if !rscData.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(rscData.Preference.ValueInt64())) + } + qualifiedNextHopNextHop := make(map[string]struct{}) + for i, block := range rscData.QualifiedNextHop { + nextHop := block.NextHop.ValueString() + if _, ok := qualifiedNextHopNextHop[nextHop]; ok { + return path.Root("qualified_next_hop").AtListIndex(i).AtName("next_hop"), + fmt.Errorf("multiple qualified_next_hop blocks with the same next_hop %q", nextHop) + } + qualifiedNextHopNextHop[nextHop] = struct{}{} + + setPrefixQualifiedNextHop := setPrefix + "qualified-next-hop " + nextHop + configSet = append(configSet, setPrefixQualifiedNextHop) + setPrefixQualifiedNextHop += " " + if v := block.Interface.ValueString(); v != "" { + configSet = append(configSet, setPrefixQualifiedNextHop+"interface "+v) + } + if !block.Metric.IsNull() { + configSet = append(configSet, setPrefixQualifiedNextHop+"metric "+ + utils.ConvI64toa(block.Metric.ValueInt64())) + } + if !block.Preference.IsNull() { + configSet = append(configSet, setPrefixQualifiedNextHop+"preference "+ + utils.ConvI64toa(block.Preference.ValueInt64())) + } + } + if rscData.Readvertise.ValueBool() { + configSet = append(configSet, setPrefix+"readvertise") + } + if rscData.NoReadvertise.ValueBool() { + configSet = append(configSet, setPrefix+"no-readvertise") + } + if rscData.Receive.ValueBool() { + configSet = append(configSet, setPrefix+"receive") + } + if rscData.Reject.ValueBool() { + configSet = append(configSet, setPrefix+"reject") + } + if rscData.Resolve.ValueBool() { + configSet = append(configSet, setPrefix+"resolve") + } + if rscData.NoResolve.ValueBool() { + configSet = append(configSet, setPrefix+"no-resolve") + } + if rscData.Retain.ValueBool() { + configSet = append(configSet, setPrefix+"retain") + } + if rscData.NoRetain.ValueBool() { + configSet = append(configSet, setPrefix+"no-retain") + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *staticRouteData) read( + _ context.Context, destination, routingInstance string, junSess *junos.Session, +) ( + err error, +) { + showPrefix := junos.CmdShowConfig + switch routingInstance { + case junos.DefaultW, "": + showPrefix += junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += junos.RibInet60WS + } + default: + showPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(destination, ":") { + showPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + showConfig, err := junSess.Command(showPrefix + "static route " + destination + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + + if showConfig != junos.EmptyW { + rscData.Destination = types.StringValue(destination) + rscData.RoutingInstance = types.StringValue(routingInstance) + if rscData.RoutingInstance.ValueString() == "" { + rscData.RoutingInstance = types.StringValue(junos.DefaultW) + } + 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 == "active": + rscData.Active = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { //
+ return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) + } + rscData.ASPathAggregatorASNumber = types.StringValue(itemTrimFields[0]) + rscData.ASPathAggregatorAddress = types.StringValue(itemTrimFields[1]) + case itemTrim == "as-path atomic-aggregate": + rscData.ASPathAtomicAggregate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path origin "): + rscData.ASPathOrigin = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "as-path path "): + rscData.ASPathPath = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "community "): + rscData.Community = append(rscData.Community, types.StringValue(strings.Trim(itemTrim, "\""))) + case itemTrim == junos.DiscardW: + rscData.Discard = types.BoolValue(true) + case itemTrim == "install": + rscData.Install = types.BoolValue(true) + case itemTrim == "no-install": + rscData.NoInstall = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "metric "): + rscData.Metric, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "next-hop "): + rscData.NextHop = append(rscData.NextHop, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "next-table "): + rscData.NextTable = types.StringValue(strings.Trim(itemTrim, "\"")) + case itemTrim == "passive": + rscData.Passive = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "preference "): + rscData.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "qualified-next-hop "): + itemTrimFields := strings.Split(itemTrim, " ") + var qualifiedNextHop staticRouteBlockQualifiedNextHop + rscData.QualifiedNextHop, qualifiedNextHop = tfdata.ExtractBlockWithTFTypesString( + rscData.QualifiedNextHop, "NextHop", itemTrimFields[0], + ) + qualifiedNextHop.NextHop = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + switch { + case balt.CutPrefixInString(&itemTrim, "interface "): + qualifiedNextHop.Interface = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "metric "): + qualifiedNextHop.Metric, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "preference "): + qualifiedNextHop.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + rscData.QualifiedNextHop = append(rscData.QualifiedNextHop, qualifiedNextHop) + case itemTrim == "readvertise": + rscData.Readvertise = types.BoolValue(true) + case itemTrim == "no-readvertise": + rscData.NoReadvertise = types.BoolValue(true) + case itemTrim == "receive": + rscData.Receive = types.BoolValue(true) + case itemTrim == "reject": + rscData.Reject = types.BoolValue(true) + case itemTrim == "resolve": + rscData.Resolve = types.BoolValue(true) + case itemTrim == "no-resolve": + rscData.NoResolve = types.BoolValue(true) + case itemTrim == "retain": + rscData.Retain = types.BoolValue(true) + case itemTrim == "no-retain": + rscData.NoRetain = types.BoolValue(true) + } + } + } + + return nil +} + +func (rscData *staticRouteData) del( + _ context.Context, junSess *junos.Session, +) error { + delPrefix := junos.DeleteLS + switch routingInstance := rscData.RoutingInstance.ValueString(); routingInstance { + case junos.DefaultW, "": + delPrefix += junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += junos.RibInet60WS + } + default: + delPrefix += junos.RoutingInstancesWS + routingInstance + " " + junos.RoutingOptionsWS + if strings.Contains(rscData.Destination.ValueString(), ":") { + delPrefix += "rib " + routingInstance + ".inet6.0 " + } + } + configSet := []string{ + delPrefix + "static route " + rscData.Destination.ValueString(), + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_static_route_test.go b/internal/providerfwk/resource_static_route_test.go similarity index 98% rename from internal/providersdk/resource_static_route_test.go rename to internal/providerfwk/resource_static_route_test.go index a6b3e88e..eca50492 100644 --- a/internal/providersdk/resource_static_route_test.go +++ b/internal/providerfwk/resource_static_route_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 TestAccJunosStaticRoute_basic(t *testing.T) { @@ -250,7 +250,7 @@ resource "junos_static_route" "testacc_staticRoute_instance" { community = ["no-advertise"] } resource "junos_static_route" "testacc_staticRoute_default" { - destination = "192.0.2.0/24" + destination = "192.0.2.0/25" preference = 100 metric = 100 next_hop = ["st0.0"] @@ -271,6 +271,12 @@ resource "junos_static_route" "testacc_staticRoute_default" { as_path_origin = "igp" as_path_path = "65000 65000" } +resource "junos_static_route" "testacc_staticRoute2_default" { + destination = "192.0.2.128/28" + next_hop = [ + "192.0.2.254" + ] +} resource "junos_static_route" "testacc_staticRoute_ipv6_default" { destination = "2001:db8:85a3::/48" preference = 100 diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index fd42441f..1e083b23 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -201,7 +201,6 @@ func Provider() *schema.Provider { "junos_snmp_v3_vacm_accessgroup": resourceSnmpV3VacmAccessGroup(), "junos_snmp_v3_vacm_securitytogroup": resourceSnmpV3VacmSecurityToGroup(), "junos_snmp_view": resourceSnmpView(), - "junos_static_route": resourceStaticRoute(), "junos_switch_options": resourceSwitchOptions(), "junos_system": resourceSystem(), "junos_system_login_class": resourceSystemLoginClass(), diff --git a/internal/providersdk/resource_static_route.go b/internal/providersdk/resource_static_route.go deleted file mode 100644 index c374353d..00000000 --- a/internal/providersdk/resource_static_route.go +++ /dev/null @@ -1,824 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strconv" - "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" - bchk "github.com/jeremmfr/go-utils/basiccheck" -) - -type staticRouteOptions struct { - active bool - asPathAtomicAggregate bool - discard bool - install bool - noInstall bool - passive bool - readvertise bool - noReadvertise bool - receive bool - reject bool - resolve bool - noResolve bool - retain bool - noRetain bool - preference int - metric int - asPathAggregatorAddress string - asPathAggregatorAsNumber string - asPathOrigin string - asPathPath string - destination string - routingInstance string - nextTable string - community []string - nextHop []string - qualifiedNextHop []map[string]interface{} -} - -func resourceStaticRoute() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceStaticRouteCreate, - ReadWithoutTimeout: resourceStaticRouteRead, - UpdateWithoutTimeout: resourceStaticRouteUpdate, - DeleteWithoutTimeout: resourceStaticRouteDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceStaticRouteImport, - }, - Schema: map[string]*schema.Schema{ - "destination": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.IsCIDRNetwork(0, 128), - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: junos.DefaultW, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "active": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"passive"}, - }, - "as_path_aggregator_address": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_as_number"}, - ValidateFunc: validation.IsIPAddress, - }, - "as_path_aggregator_as_number": { - Type: schema.TypeString, - Optional: true, - RequiredWith: []string{"as_path_aggregator_address"}, - }, - "as_path_atomic_aggregate": { - Type: schema.TypeBool, - Optional: true, - }, - "as_path_origin": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"egp", "igp", "incomplete"}, false), - }, - "as_path_path": { - Type: schema.TypeString, - Optional: true, - }, - "community": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "discard": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"receive", "reject", "next_hop", "next_table", "qualified_next_hop"}, - }, - "install": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_install"}, - }, - "no_install": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"install"}, - }, - "metric": { - Type: schema.TypeInt, - Optional: true, - }, - "next_hop": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"next_table", "discard", "receive", "reject"}, - }, - "next_table": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"next_hop", "qualified_next_hop", "discard", "receive", "reject"}, - }, - "passive": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"active"}, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - }, - "qualified_next_hop": { - Type: schema.TypeList, - Optional: true, - ConflictsWith: []string{"next_table", "discard", "receive", "reject"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "next_hop": { - Type: schema.TypeString, - Required: true, - }, - "interface": { - Type: schema.TypeString, - Optional: true, - }, - "metric": { - Type: schema.TypeInt, - Optional: true, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - }, - }, - }, - }, - "readvertise": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_readvertise"}, - }, - "no_readvertise": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"readvertise"}, - }, - "receive": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"discard", "reject", "next_hop", "next_table", "qualified_next_hop"}, - }, - "reject": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"discard", "receive", "next_hop", "next_table", "qualified_next_hop"}, - }, - "resolve": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_resolve", "retain", "no_retain"}, - }, - "no_resolve": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"resolve"}, - }, - "retain": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_retain", "resolve"}, - }, - "no_retain": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"retain", "resolve"}, - }, - }, - } -} - -func resourceStaticRouteCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setStaticRoute(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("destination").(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)))...) - } - } - staticRouteExists, err := checkStaticRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if staticRouteExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("static route %v already exists on table %s", - d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - if err := setStaticRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_static_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - staticRouteExists, err = checkStaticRouteExists( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if staticRouteExists { - d.SetId(d.Get("destination").(string) + junos.IDSeparator + d.Get("routing_instance").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("static route %v not exists in routing_instance %v after commit "+ - "=> check your config", d.Get("destination").(string), d.Get("routing_instance").(string)))...) - } - - return append(diagWarns, resourceStaticRouteReadWJunSess(d, junSess)...) -} - -func resourceStaticRouteRead(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 resourceStaticRouteReadWJunSess(d, junSess) -} - -func resourceStaticRouteReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - staticRouteOptions, err := readStaticRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if staticRouteOptions.destination == "" { - d.SetId("") - } else { - fillStaticRouteData(d, staticRouteOptions) - } - - return nil -} - -func resourceStaticRouteUpdate(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 := delStaticRoute(d.Get("destination").(string), d.Get("routing_instance").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setStaticRoute(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 := delStaticRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setStaticRoute(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_static_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - d.Partial(false) - - return append(diagWarns, resourceStaticRouteReadWJunSess(d, junSess)...) -} - -func resourceStaticRouteDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delStaticRoute(d.Get("destination").(string), d.Get("routing_instance").(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 := delStaticRoute( - d.Get("destination").(string), - d.Get("routing_instance").(string), - junSess, - ); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_static_route") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceStaticRouteImport(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) - } - staticRouteExists, err := checkStaticRouteExists(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - if !staticRouteExists { - return nil, fmt.Errorf("don't find static route with id '%v' (id must be "+ - ""+junos.IDSeparator+")", d.Id()) - } - staticRouteOptions, err := readStaticRoute(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - fillStaticRouteData(d, staticRouteOptions) - - result[0] = d - - return result, nil -} - -func checkStaticRouteExists(destination, instance string, junSess *junos.Session, -) (_ bool, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options static route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 static route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options static route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 static route " + destination + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - } - - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setStaticRoute(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - var setPrefix string - if d.Get("routing_instance").(string) == junos.DefaultW { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = "set routing-options static route " + d.Get("destination").(string) - } else { - setPrefix = "set routing-options rib inet6.0 static route " + d.Get("destination").(string) - } - } else { - if !strings.Contains(d.Get("destination").(string), ":") { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options static route " + d.Get("destination").(string) - } else { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + - " routing-options rib " + d.Get("routing_instance").(string) + ".inet6.0 " + - "static route " + d.Get("destination").(string) - } - } - if d.Get("active").(bool) { - configSet = append(configSet, setPrefix+" active") - } - if d.Get("as_path_aggregator_address").(string) != "" && - d.Get("as_path_aggregator_as_number").(string) != "" { - configSet = append(configSet, setPrefix+" as-path aggregator "+ - d.Get("as_path_aggregator_as_number").(string)+" "+ - d.Get("as_path_aggregator_address").(string)) - } - if d.Get("as_path_atomic_aggregate").(bool) { - configSet = append(configSet, setPrefix+" as-path atomic-aggregate") - } - if v := d.Get("as_path_origin").(string); v != "" { - configSet = append(configSet, setPrefix+" as-path origin "+v) - } - if v := d.Get("as_path_path").(string); v != "" { - configSet = append(configSet, setPrefix+" as-path path \""+v+"\"") - } - for _, v := range d.Get("community").([]interface{}) { - configSet = append(configSet, setPrefix+" community "+v.(string)) - } - if d.Get("discard").(bool) { - configSet = append(configSet, setPrefix+" discard") - } - if d.Get("install").(bool) { - configSet = append(configSet, setPrefix+" install") - } - if d.Get("no_install").(bool) { - configSet = append(configSet, setPrefix+" no-install") - } - if d.Get("metric").(int) > 0 { - configSet = append(configSet, setPrefix+" metric "+strconv.Itoa(d.Get("metric").(int))) - } - for _, nextHop := range d.Get("next_hop").([]interface{}) { - configSet = append(configSet, setPrefix+" next-hop "+nextHop.(string)) - } - if d.Get("next_table").(string) != "" { - configSet = append(configSet, setPrefix+" next-table "+d.Get("next_table").(string)) - } - if d.Get("passive").(bool) { - configSet = append(configSet, setPrefix+" passive") - } - if d.Get("preference").(int) > 0 { - configSet = append(configSet, setPrefix+" preference "+strconv.Itoa(d.Get("preference").(int))) - } - qualifiedNextHopList := make([]string, 0) - for _, qualifiedNextHop := range d.Get("qualified_next_hop").([]interface{}) { - qualifiedNextHopMap := qualifiedNextHop.(map[string]interface{}) - if bchk.InSlice(qualifiedNextHopMap["next_hop"].(string), qualifiedNextHopList) { - return fmt.Errorf("multiple blocks qualified_next_hop with the same next_hop %s", - qualifiedNextHopMap["next_hop"].(string)) - } - qualifiedNextHopList = append(qualifiedNextHopList, qualifiedNextHopMap["next_hop"].(string)) - configSet = append(configSet, setPrefix+" qualified-next-hop "+qualifiedNextHopMap["next_hop"].(string)) - if qualifiedNextHopMap["interface"] != "" { - configSet = append(configSet, setPrefix+ - " qualified-next-hop "+qualifiedNextHopMap["next_hop"].(string)+ - " interface "+qualifiedNextHopMap["interface"].(string)) - } - if qualifiedNextHopMap["metric"].(int) > 0 { - configSet = append(configSet, setPrefix+ - " qualified-next-hop "+qualifiedNextHopMap["next_hop"].(string)+ - " metric "+strconv.Itoa(qualifiedNextHopMap["metric"].(int))) - } - if qualifiedNextHopMap["preference"].(int) > 0 { - configSet = append(configSet, setPrefix+ - " qualified-next-hop "+qualifiedNextHopMap["next_hop"].(string)+ - " preference "+strconv.Itoa(qualifiedNextHopMap["preference"].(int))) - } - } - if d.Get("readvertise").(bool) { - configSet = append(configSet, setPrefix+" readvertise") - } - if d.Get("no_readvertise").(bool) { - configSet = append(configSet, setPrefix+" no-readvertise") - } - if d.Get("receive").(bool) { - configSet = append(configSet, setPrefix+" receive") - } - if d.Get("reject").(bool) { - configSet = append(configSet, setPrefix+" reject") - } - if d.Get("resolve").(bool) { - configSet = append(configSet, setPrefix+" resolve") - } - if d.Get("no_resolve").(bool) { - configSet = append(configSet, setPrefix+" no-resolve") - } - if d.Get("retain").(bool) { - configSet = append(configSet, setPrefix+" retain") - } - if d.Get("no_retain").(bool) { - configSet = append(configSet, setPrefix+" no-retain") - } - - return junSess.ConfigSet(configSet) -} - -func readStaticRoute(destination, instance string, junSess *junos.Session, -) (confRead staticRouteOptions, err error) { - var showConfig string - if instance == junos.DefaultW { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options static route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "routing-options rib inet6.0 static route " + destination + junos.PipeDisplaySetRelative) - } - } else { - if !strings.Contains(destination, ":") { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options static route " + destination + junos.PipeDisplaySetRelative) - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "routing-options rib " + instance + ".inet6.0 static route " + destination + junos.PipeDisplaySetRelative) - } - } - if err != nil { - return confRead, err - } - - if showConfig != junos.EmptyW { - confRead.destination = destination - 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 itemTrim == "active": - confRead.active = true - case balt.CutPrefixInString(&itemTrim, "as-path aggregator "): - itemTrimFields := strings.Split(itemTrim, " ") - if len(itemTrimFields) < 2 { //
- return confRead, fmt.Errorf(junos.CantReadValuesNotEnoughFields, "as-path aggregator", itemTrim) - } - confRead.asPathAggregatorAsNumber = itemTrimFields[0] - confRead.asPathAggregatorAddress = itemTrimFields[1] - case itemTrim == "as-path atomic-aggregate": - confRead.asPathAtomicAggregate = true - case balt.CutPrefixInString(&itemTrim, "as-path origin "): - confRead.asPathOrigin = itemTrim - case balt.CutPrefixInString(&itemTrim, "as-path path "): - confRead.asPathPath = strings.Trim(itemTrim, "\"") - case balt.CutPrefixInString(&itemTrim, "community "): - confRead.community = append(confRead.community, itemTrim) - case itemTrim == junos.DiscardW: - confRead.discard = true - case itemTrim == "install": - confRead.install = true - case itemTrim == "no-install": - confRead.noInstall = true - case balt.CutPrefixInString(&itemTrim, "metric "): - confRead.metric, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "next-hop "): - confRead.nextHop = append(confRead.nextHop, itemTrim) - case balt.CutPrefixInString(&itemTrim, "next-table "): - confRead.nextTable = itemTrim - case itemTrim == "passive": - confRead.passive = true - case balt.CutPrefixInString(&itemTrim, "preference "): - confRead.preference, err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "qualified-next-hop "): - itemTrimFields := strings.Split(itemTrim, " ") - qualifiedNextHopOptions := map[string]interface{}{ - "next_hop": itemTrimFields[0], - "interface": "", - "metric": 0, - "preference": 0, - } - confRead.qualifiedNextHop = copyAndRemoveItemMapList("next_hop", qualifiedNextHopOptions, confRead.qualifiedNextHop) - balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") - switch { - case balt.CutPrefixInString(&itemTrim, "interface "): - qualifiedNextHopOptions["interface"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "metric "): - qualifiedNextHopOptions["metric"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "preference "): - qualifiedNextHopOptions["preference"], err = strconv.Atoi(itemTrim) - if err != nil { - return confRead, fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - confRead.qualifiedNextHop = append(confRead.qualifiedNextHop, qualifiedNextHopOptions) - case itemTrim == "readvertise": - confRead.readvertise = true - case itemTrim == "no-readvertise": - confRead.noReadvertise = true - case itemTrim == "receive": - confRead.receive = true - case itemTrim == "reject": - confRead.reject = true - case itemTrim == "resolve": - confRead.resolve = true - case itemTrim == "no-resolve": - confRead.noResolve = true - case itemTrim == "retain": - confRead.retain = true - case itemTrim == "no-retain": - confRead.noRetain = true - } - } - } - - return confRead, nil -} - -func delStaticRoute(destination, instance string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - switch { - case instance == junos.DefaultW && !strings.Contains(destination, ":"): - configSet = append(configSet, "delete routing-options static route "+destination) - case instance == junos.DefaultW && strings.Contains(destination, ":"): - configSet = append(configSet, "delete routing-options rib inet6.0 static route "+destination) - case instance != junos.DefaultW && !strings.Contains(destination, ":"): - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options static route "+destination) - case instance != junos.DefaultW && strings.Contains(destination, ":"): - configSet = append(configSet, junos.DelRoutingInstances+instance+" "+ - "routing-options rib "+instance+".inet6.0 static route "+destination) - } - - return junSess.ConfigSet(configSet) -} - -func fillStaticRouteData(d *schema.ResourceData, staticRouteOptions staticRouteOptions) { - if tfErr := d.Set("destination", staticRouteOptions.destination); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", staticRouteOptions.routingInstance); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("active", staticRouteOptions.active); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_address", staticRouteOptions.asPathAggregatorAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_aggregator_as_number", staticRouteOptions.asPathAggregatorAsNumber); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_atomic_aggregate", staticRouteOptions.asPathAtomicAggregate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_origin", staticRouteOptions.asPathOrigin); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path_path", staticRouteOptions.asPathPath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("community", staticRouteOptions.community); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("discard", staticRouteOptions.discard); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("install", staticRouteOptions.install); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_install", staticRouteOptions.noInstall); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric", staticRouteOptions.metric); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("next_hop", staticRouteOptions.nextHop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("next_table", staticRouteOptions.nextTable); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("passive", staticRouteOptions.passive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("preference", staticRouteOptions.preference); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("qualified_next_hop", staticRouteOptions.qualifiedNextHop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("readvertise", staticRouteOptions.readvertise); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_readvertise", staticRouteOptions.noReadvertise); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("receive", staticRouteOptions.receive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("reject", staticRouteOptions.reject); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("resolve", staticRouteOptions.resolve); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_resolve", staticRouteOptions.noResolve); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("retain", staticRouteOptions.retain); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_retain", staticRouteOptions.noRetain); tfErr != nil { - panic(tfErr) - } -} From a9aa4d6af261d264705af4bfb8a423c66cb1dc12 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 4 Sep 2023 14:17:21 +0200 Subject: [PATCH 40/48] r/security_nat_static: ValidateConfig: do not read rule if unknown --- internal/providerfwk/resource_security_nat_static.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providerfwk/resource_security_nat_static.go b/internal/providerfwk/resource_security_nat_static.go index 7349bf1d..6378b6eb 100644 --- a/internal/providerfwk/resource_security_nat_static.go +++ b/internal/providerfwk/resource_security_nat_static.go @@ -364,7 +364,7 @@ func (rsc *securityNatStatic) ValidateConfig( ) } - if !config.Rule.IsNull() { + if !config.Rule.IsNull() && !config.Rule.IsUnknown() { var rule []securityNatStaticBlockRuleConfig asDiags := config.Rule.ElementsAs(ctx, &rule, false) if asDiags.HasError() { From 6d079f64a075de9b4ef24a9765a81c7ff8098310 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 4 Sep 2023 17:22:14 +0200 Subject: [PATCH 41/48] d/routes: use new provider via framework --- .changes/route-with-fwk.md | 1 + internal/junos/netconf.go | 18 +- internal/providerfwk/data_source_routes.go | 250 ++++++++++++++++++ .../data_source_routes_test.go | 4 +- internal/providerfwk/provider.go | 1 + internal/providersdk/data_source_routes.go | 196 -------------- internal/providersdk/provider.go | 1 - 7 files changed, 263 insertions(+), 208 deletions(-) create mode 100644 internal/providerfwk/data_source_routes.go rename internal/{providersdk => providerfwk}/data_source_routes_test.go (97%) delete mode 100644 internal/providersdk/data_source_routes.go diff --git a/.changes/route-with-fwk.md b/.changes/route-with-fwk.md index a78c3b0e..6362eafd 100644 --- a/.changes/route-with-fwk.md +++ b/.changes/route-with-fwk.md @@ -11,3 +11,4 @@ ENHANCEMENTS: 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 +* **data-source/junos_routes**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) diff --git a/internal/junos/netconf.go b/internal/junos/netconf.go index 64f7a925..73b5b784 100644 --- a/internal/junos/netconf.go +++ b/internal/junos/netconf.go @@ -89,19 +89,19 @@ type GetRouteInformationReply struct { Route []struct { Destination string `xml:"rt-destination"` Entry []struct { - ASPath string `xml:"as-path"` + ASPath *string `xml:"as-path"` CurrentActive *struct{} `xml:"current-active"` - LocalPreference int `xml:"local-preference"` - Metric int `xml:"metric"` + LocalPreference *int `xml:"local-preference"` + Metric *int `xml:"metric"` NextHop []struct { SelectedNextHop *struct{} `xml:"selected-next-hop"` - LocalInterface string `xml:"nh-local-interface"` - To string `xml:"to"` - Via string `xml:"via"` + LocalInterface *string `xml:"nh-local-interface"` + To *string `xml:"to"` + Via *string `xml:"via"` } `xml:"nh"` - NextHopType string `xml:"nh-type"` - Preference int `xml:"preference"` - Protocol string `xml:"protocol-name"` + NextHopType *string `xml:"nh-type"` + Preference *int `xml:"preference"` + Protocol *string `xml:"protocol-name"` } `xml:"rt-entry"` } `xml:"rt"` } `xml:"route-table"` diff --git a/internal/providerfwk/data_source_routes.go b/internal/providerfwk/data_source_routes.go new file mode 100644 index 00000000..a3272ebe --- /dev/null +++ b/internal/providerfwk/data_source_routes.go @@ -0,0 +1,250 @@ +package providerfwk + +import ( + "context" + "encoding/xml" + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &routesDataSource{} + _ datasource.DataSourceWithConfigure = &routesDataSource{} +) + +type routesDataSource struct { + client *junos.Client +} + +func (dsc *routesDataSource) typeName() string { + return providerName + "_routes" +} + +func (dsc *routesDataSource) junosName() string { + return "present routes" +} + +func newRoutesDataSource() datasource.DataSource { + return &routesDataSource{} +} + +func (dsc *routesDataSource) Metadata( + _ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse, +) { + resp.TypeName = dsc.typeName() +} + +func (dsc *routesDataSource) Configure( + ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedDataSourceConfigureType(ctx, req, resp) + + return + } + dsc.client = client +} + +func (dsc *routesDataSource) Schema( + _ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Get " + dsc.junosName() + " in table(s).", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the data source.", + }, + "table_name": schema.StringAttribute{ + Optional: true, + Description: "Get routes only on a specific routing table with the name.", + }, + "table": schema.ListAttribute{ + Computed: true, + Description: "For each routing table.", + ElementType: types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "name": types.StringType, + "route": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "destination": types.StringType, + "entry": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "as_path": types.StringType, + "current_active": types.BoolType, + "local_preference": types.Int64Type, + "metric": types.Int64Type, + "next_hop": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(map[string]attr.Type{ + "local_interface": types.StringType, + "selected_next_hop": types.BoolType, + "to": types.StringType, + "via": types.StringType, + })), + "next_hop_type": types.StringType, + "preference": types.Int64Type, + "protocol": types.StringType, + })), + })), + }), + }, + }, + } +} + +type routesDataSourceData struct { + ID types.String `tfsdk:"id"` + TableName types.String `tfsdk:"table_name"` + Table []routesDataSourceBlockTable `tfsdk:"table"` +} + +type routesDataSourceBlockTable struct { + Name types.String `tfsdk:"name"` + Route []routesDataSourceBlockTableBlockRoute `tfsdk:"route"` +} + +type routesDataSourceBlockTableBlockRoute struct { + Destination types.String `tfsdk:"destination"` + Entry []routesDataSourceBlockTableBlockRouteBlockEntry `tfsdk:"entry"` +} + +type routesDataSourceBlockTableBlockRouteBlockEntry struct { + ASPath types.String `tfsdk:"as_path"` + CurrentActive types.Bool `tfsdk:"current_active"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + Metric types.Int64 `tfsdk:"metric"` + NextHop []routesDataSourceBlockTableBlockRouteBlockEntryBlockNextHop `tfsdk:"next_hop"` + NextHopType types.String `tfsdk:"next_hop_type"` + Preference types.Int64 `tfsdk:"preference"` + Protocol types.String `tfsdk:"protocol"` +} + +type routesDataSourceBlockTableBlockRouteBlockEntryBlockNextHop struct { + LocalInterface types.String `tfsdk:"local_interface"` + SelectedNextHop types.Bool `tfsdk:"selected_next_hop"` + To types.String `tfsdk:"to"` + Via types.String `tfsdk:"via"` +} + +func (dsc *routesDataSource) Read( + ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse, +) { + var data routesDataSourceData + var tableName types.String + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("table_name"), &tableName)...) + if resp.Diagnostics.HasError() { + return + } + data.TableName = tableName + + junSess, err := dsc.client.StartNewSession(ctx) + if err != nil { + resp.Diagnostics.AddError(tfdiag.StartSessErrSummary, err.Error()) + + return + } + defer junSess.Close() + + junos.MutexLock() + err = data.read(tableName.ValueString(), junSess) + junos.MutexUnlock() + if err != nil { + resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) + + return + } + + data.fillID() + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (dscData *routesDataSourceData) fillID() { + if v := dscData.TableName.ValueString(); v != "" { + dscData.ID = types.StringValue(v) + } else { + dscData.ID = types.StringValue("all") + } +} + +func (dscData *routesDataSourceData) read( + tableName string, junSess *junos.Session, +) error { + rpcReq := junos.RPCGetRouteAllInformation + if tableName != "" { + rpcReq = fmt.Sprintf(junos.RPCGetRouteAllTableInformation, tableName) + } + replyData, err := junSess.CommandXML(rpcReq) + if err != nil { + return err + } + var routeTable junos.GetRouteInformationReply + err = xml.Unmarshal([]byte(replyData), &routeTable.RouteInfo) + if err != nil { + return fmt.Errorf("unmarshaling xml reply '%s': %w", replyData, err) + } + + for _, tableInfo := range routeTable.RouteInfo.RouteTable { + table := routesDataSourceBlockTable{ + Name: types.StringValue(tableInfo.TableName), + } + for _, routeInfo := range tableInfo.Route { + route := routesDataSourceBlockTableBlockRoute{ + Destination: types.StringValue(routeInfo.Destination), + } + for _, entryInfo := range routeInfo.Entry { + entry := routesDataSourceBlockTableBlockRouteBlockEntry{ + CurrentActive: types.BoolValue(entryInfo.CurrentActive != nil), + } + if entryInfo.ASPath != nil { + entry.ASPath = types.StringValue(strings.Trim(*entryInfo.ASPath, "\n")) + } + if entryInfo.LocalPreference != nil { + entry.LocalPreference = types.Int64Value(int64(*entryInfo.LocalPreference)) + } + if entryInfo.Metric != nil { + entry.Metric = types.Int64Value(int64(*entryInfo.Metric)) + } + if entryInfo.NextHopType != nil { + entry.NextHopType = types.StringValue(*entryInfo.NextHopType) + } + if entryInfo.Preference != nil { + entry.Preference = types.Int64Value(int64(*entryInfo.Preference)) + } + if entryInfo.Protocol != nil { + entry.Protocol = types.StringValue(*entryInfo.Protocol) + } + for _, nextHopInfo := range entryInfo.NextHop { + nextHop := routesDataSourceBlockTableBlockRouteBlockEntryBlockNextHop{ + SelectedNextHop: types.BoolValue(nextHopInfo.SelectedNextHop != nil), + } + if nextHopInfo.LocalInterface != nil { + nextHop.LocalInterface = types.StringValue(*nextHopInfo.LocalInterface) + } + if nextHopInfo.To != nil { + nextHop.To = types.StringValue(*nextHopInfo.To) + } + if nextHopInfo.Via != nil { + nextHop.Via = types.StringValue(*nextHopInfo.Via) + } + entry.NextHop = append(entry.NextHop, nextHop) + } + route.Entry = append(route.Entry, entry) + } + table.Route = append(table.Route, route) + } + dscData.Table = append(dscData.Table, table) + } + + return nil +} diff --git a/internal/providersdk/data_source_routes_test.go b/internal/providerfwk/data_source_routes_test.go similarity index 97% rename from internal/providersdk/data_source_routes_test.go rename to internal/providerfwk/data_source_routes_test.go index c878c21f..9ee77a8a 100644 --- a/internal/providersdk/data_source_routes_test.go +++ b/internal/providerfwk/data_source_routes_test.go @@ -1,4 +1,4 @@ -package providersdk_test +package providerfwk_test import ( "fmt" @@ -7,7 +7,7 @@ import ( "github.com/jeremmfr/terraform-provider-junos/internal/junos" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceRoutes_basic(t *testing.T) { diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 4220568c..025f5561 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -192,6 +192,7 @@ func (p *junosProvider) DataSources(_ context.Context) []func() datasource.DataS newInterfaceLogicalInfoDataSource, newInterfacePhysicalDataSource, newInterfacesPhysicalPresentDataSource, + newRoutesDataSource, newRoutingInstanceDataSource, newSecurityZoneDataSource, } diff --git a/internal/providersdk/data_source_routes.go b/internal/providersdk/data_source_routes.go deleted file mode 100644 index 73da4ba5..00000000 --- a/internal/providersdk/data_source_routes.go +++ /dev/null @@ -1,196 +0,0 @@ -package providersdk - -import ( - "context" - "encoding/xml" - "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 routesTableOpts struct { - table []map[string]interface{} -} - -func dataSourceRoutes() *schema.Resource { - return &schema.Resource{ - ReadWithoutTimeout: dataSourceRoutesRead, - Schema: map[string]*schema.Schema{ - "table_name": { - Type: schema.TypeString, - Optional: true, - }, - "table": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - }, - "route": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "destination": { - Type: schema.TypeString, - Computed: true, - }, - "entry": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "as_path": { - Type: schema.TypeString, - Computed: true, - }, - "current_active": { - Type: schema.TypeBool, - Computed: true, - }, - "local_preference": { - Type: schema.TypeInt, - Computed: true, - }, - "metric": { - Type: schema.TypeInt, - Computed: true, - }, - "next_hop": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "local_interface": { - Type: schema.TypeString, - Computed: true, - }, - "selected_next_hop": { - Type: schema.TypeBool, - Computed: true, - }, - "to": { - Type: schema.TypeString, - Computed: true, - }, - "via": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "next_hop_type": { - Type: schema.TypeString, - Computed: true, - }, - "preference": { - Type: schema.TypeInt, - Computed: true, - }, - "protocol": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func dataSourceRoutesRead(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() - junos.MutexLock() - routesTable, err := searchRoutes(d, junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if tfErr := d.Set("table", routesTable.table); tfErr != nil { - panic(tfErr) - } - if v := d.Get("table_name").(string); v != "" { - d.SetId(v) - } else { - d.SetId("all") - } - - return nil -} - -func searchRoutes(d *schema.ResourceData, junSess *junos.Session, -) (routesTableOpts, error) { - var result routesTableOpts - rpcReq := junos.RPCGetRouteAllInformation - if v := d.Get("table_name").(string); v != "" { - rpcReq = fmt.Sprintf(junos.RPCGetRouteAllTableInformation, v) - } - replyData, err := junSess.CommandXML(rpcReq) - if err != nil { - return result, err - } - var routeTable junos.GetRouteInformationReply - err = xml.Unmarshal([]byte(replyData), &routeTable.RouteInfo) - if err != nil { - return result, fmt.Errorf("unmarshaling xml reply '%s': %w", replyData, err) - } - for _, tableInfo := range routeTable.RouteInfo.RouteTable { - table := map[string]interface{}{ - "name": tableInfo.TableName, - "route": make([]map[string]interface{}, 0, len(tableInfo.Route)), - } - for _, routeInfo := range tableInfo.Route { - route := map[string]interface{}{ - "destination": routeInfo.Destination, - "entry": make([]map[string]interface{}, 0, len(routeInfo.Entry)), - } - for _, entryInfo := range routeInfo.Entry { - entry := map[string]interface{}{ - "as_path": strings.Trim(entryInfo.ASPath, "\n"), - "current_active": entryInfo.CurrentActive != nil, - "local_preference": entryInfo.LocalPreference, - "metric": entryInfo.Metric, - "next_hop": make([]map[string]interface{}, 0, len(entryInfo.NextHop)), - "next_hop_type": entryInfo.NextHopType, - "preference": entryInfo.Preference, - "protocol": entryInfo.Protocol, - } - for _, nextHopInfo := range entryInfo.NextHop { - entry["next_hop"] = append( - entry["next_hop"].([]map[string]interface{}), - map[string]interface{}{ - "local_interface": nextHopInfo.LocalInterface, - "selected_next_hop": nextHopInfo.SelectedNextHop != nil, - "to": nextHopInfo.To, - "via": nextHopInfo.Via, - }) - } - route["entry"] = append(route["entry"].([]map[string]interface{}), entry) - } - table["route"] = append(table["route"].([]map[string]interface{}), route) - } - result.table = append(result.table, table) - } - - return result, nil -} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 1e083b23..70dba59c 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -218,7 +218,6 @@ func Provider() *schema.Provider { "junos_vstp_vlan_group": resourceVstpVlanGroup(), }, DataSourcesMap: map[string]*schema.Resource{ - "junos_routes": dataSourceRoutes(), "junos_system_information": dataSourceSystemInformation(), }, ConfigureContextFunc: configureProvider, From 1677ae4de0cd6c50937f81cf0c75ddac3e9c6b0f Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 4 Sep 2023 18:02:21 +0200 Subject: [PATCH 42/48] d/*: unexport internal functions --- internal/providerfwk/data_source_application_sets.go | 8 ++++---- internal/providerfwk/data_source_applications.go | 8 ++++---- internal/providerfwk/data_source_interface_logical.go | 4 ++-- internal/providerfwk/data_source_interface_physical.go | 4 ++-- internal/providerfwk/data_source_routing_instance.go | 4 ++-- internal/providerfwk/data_source_security_zone.go | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/providerfwk/data_source_application_sets.go b/internal/providerfwk/data_source_application_sets.go index aca8732b..de1f10b5 100644 --- a/internal/providerfwk/data_source_application_sets.go +++ b/internal/providerfwk/data_source_application_sets.go @@ -144,7 +144,7 @@ func (dsc *applicationSetsDataSource) Read( defer junSess.Close() junos.MutexLock() - applicationSetMap, err := dsc.Search(junSess) + applicationSetMap, err := dsc.search(junSess) junos.MutexUnlock() if err != nil { resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) @@ -152,7 +152,7 @@ func (dsc *applicationSetsDataSource) Read( return } - if err := data.Filter(applicationSetMap); err != nil { + if err := data.filter(applicationSetMap); err != nil { resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) return @@ -163,7 +163,7 @@ func (dsc *applicationSetsDataSource) Read( resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } -func (dsc *applicationSetsDataSource) Search( +func (dsc *applicationSetsDataSource) search( junSess *junos.Session, ) ( map[string]applicationSetsDataSourceBlockApplicationSets, error, @@ -214,7 +214,7 @@ func (dsc *applicationSetsDataSource) Search( return results, nil } -func (dscData *applicationSetsDataSourceData) Filter( +func (dscData *applicationSetsDataSourceData) filter( results map[string]applicationSetsDataSourceBlockApplicationSets, ) error { if v := dscData.MatchName.ValueString(); v != "" { diff --git a/internal/providerfwk/data_source_applications.go b/internal/providerfwk/data_source_applications.go index d8fdbf4a..22cbb1a4 100644 --- a/internal/providerfwk/data_source_applications.go +++ b/internal/providerfwk/data_source_applications.go @@ -260,7 +260,7 @@ func (dsc *applicationsDataSource) Read( defer junSess.Close() junos.MutexLock() - applicationMap, err := dsc.Search(junSess) + applicationMap, err := dsc.search(junSess) junos.MutexUnlock() if err != nil { resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) @@ -268,7 +268,7 @@ func (dsc *applicationsDataSource) Read( return } - if err := data.Filter(applicationMap); err != nil { + if err := data.filter(applicationMap); err != nil { resp.Diagnostics.AddError(tfdiag.ReadErrSummary, err.Error()) return @@ -279,7 +279,7 @@ func (dsc *applicationsDataSource) Read( resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } -func (dsc *applicationsDataSource) Search( +func (dsc *applicationsDataSource) search( junSess *junos.Session, ) ( map[string]applicationsDataSourceBlockApplications, error, @@ -402,7 +402,7 @@ func (block *applicationsDataSourceBlockApplicationsBlockTerm) read(itemTrim str return nil } -func (dscData *applicationsDataSourceData) Filter( +func (dscData *applicationsDataSourceData) filter( results map[string]applicationsDataSourceBlockApplications, ) error { if v := dscData.MatchName.ValueString(); v != "" { diff --git a/internal/providerfwk/data_source_interface_logical.go b/internal/providerfwk/data_source_interface_logical.go index c5ad9f69..c4a9c815 100644 --- a/internal/providerfwk/data_source_interface_logical.go +++ b/internal/providerfwk/data_source_interface_logical.go @@ -353,7 +353,7 @@ func (dsc *interfaceLogicalDataSource) Read( } var data interfaceLogicalDataSourceData - data.CopyFromResourceData(rscData) + data.copyFromResourceData(rscData) data.ConfigInterface = configInterface data.Match = match @@ -407,7 +407,7 @@ func (dsc *interfaceLogicalDataSource) searchName( return intConfigList[0], nil } -func (dscData *interfaceLogicalDataSourceData) CopyFromResourceData(rscData interfaceLogicalData) { +func (dscData *interfaceLogicalDataSourceData) copyFromResourceData(rscData interfaceLogicalData) { dscData.ID = rscData.ID dscData.Name = rscData.Name dscData.Description = rscData.Description diff --git a/internal/providerfwk/data_source_interface_physical.go b/internal/providerfwk/data_source_interface_physical.go index 2dd30eb6..6076c3de 100644 --- a/internal/providerfwk/data_source_interface_physical.go +++ b/internal/providerfwk/data_source_interface_physical.go @@ -341,7 +341,7 @@ func (dsc *interfacePhysicalDataSource) Read( } var data interfacePhysicalDataSourceData - data.CopyFromResourceData(rscData) + data.copyFromResourceData(rscData) data.ConfigInterface = configInterface data.Match = match @@ -391,7 +391,7 @@ func (dsc *interfacePhysicalDataSource) searchName( return intConfigList[0], nil } -func (dscData *interfacePhysicalDataSourceData) CopyFromResourceData(rscData interfacePhysicalData) { +func (dscData *interfacePhysicalDataSourceData) copyFromResourceData(rscData interfacePhysicalData) { dscData.ID = rscData.ID dscData.Name = rscData.Name dscData.Description = rscData.Description diff --git a/internal/providerfwk/data_source_routing_instance.go b/internal/providerfwk/data_source_routing_instance.go index 901d1554..06696c81 100644 --- a/internal/providerfwk/data_source_routing_instance.go +++ b/internal/providerfwk/data_source_routing_instance.go @@ -203,11 +203,11 @@ func (dsc *routingInstanceDataSource) Read( } var data routingInstanceDataSourceData - data.CopyFromResourceData(rscData) + data.copyFromResourceData(rscData) resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } -func (dscData *routingInstanceDataSourceData) CopyFromResourceData(rscData routingInstanceData) { +func (dscData *routingInstanceDataSourceData) copyFromResourceData(rscData routingInstanceData) { dscData.ID = rscData.ID dscData.Name = rscData.Name dscData.Type = rscData.Type diff --git a/internal/providerfwk/data_source_security_zone.go b/internal/providerfwk/data_source_security_zone.go index 7a9c8568..d0f54246 100644 --- a/internal/providerfwk/data_source_security_zone.go +++ b/internal/providerfwk/data_source_security_zone.go @@ -252,11 +252,11 @@ func (dsc *securityZoneDataSource) Read( } var data securityZoneDataSourceData - data.CopyFromResourceData(rscData) + data.copyFromResourceData(rscData) resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } -func (dscData *securityZoneDataSourceData) CopyFromResourceData(rscData securityZoneData) { +func (dscData *securityZoneDataSourceData) copyFromResourceData(rscData securityZoneData) { dscData.ID = rscData.ID dscData.Name = rscData.Name dscData.AdvancePolicyBasedRoutingProfile = rscData.AdvancePolicyBasedRoutingProfile From f88f2481349944a005b45acd49e5ae1f6a84f2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:27:29 +0000 Subject: [PATCH 43/48] workflows: bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- .github/workflows/go.yml | 6 +++--- .github/workflows/go_analysis.yml | 4 ++-- .github/workflows/linters.yml | 10 +++++----- .github/workflows/releases.yml | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 10ae162f..65efc2c3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: List resource files and Test Website Files exists run: | missing="" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4a6d630a..d65dbcf9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,7 +17,7 @@ jobs: - name: Show version run: go version - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build run: go build -v . @@ -37,7 +37,7 @@ jobs: - name: Show version run: go version - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build run: go build -v . @@ -57,6 +57,6 @@ jobs: - name: Show version run: go version - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Test run: go test -v ./... diff --git a/.github/workflows/go_analysis.yml b/.github/workflows/go_analysis.yml index 39a516c7..4a181a43 100644 --- a/.github/workflows/go_analysis.yml +++ b/.github/workflows/go_analysis.yml @@ -12,7 +12,7 @@ jobs: actions: read contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Running govulncheck uses: Templum/govulncheck-action@v1.0.0 with: @@ -28,7 +28,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index ce36f440..6b222c4f 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -15,7 +15,7 @@ jobs: run: | echo "CGO_ENABLED=0" >> $GITHUB_ENV - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Markdown files linting uses: avto-dev/markdown-lint@v1 @@ -47,9 +47,9 @@ jobs: - name: Show version run: go version - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check out terrafmt code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: katbyte/terrafmt ref: v0.5.2 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: diff SingleNestedBlock and BlockRemoveNull working-directory: ./internal/providerfwk run: diff <( grep 'schema.SingleNestedBlock{' -c * ) <( grep 'BlockRemoveNull()' -c *) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index b7aebc4b..33f18d04 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -12,7 +12,7 @@ jobs: id: ${{steps.create_release.outputs.id}} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set env RELEASE_VERSION run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF} | cut -d'/' -f3)" >> $GITHUB_ENV - name: Create Release @@ -47,7 +47,7 @@ jobs: - name: Show version run: go version - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set env run: | echo "CGO_ENABLED=0" >> $GITHUB_ENV @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install utils run: | sudo apt update From e47c133b60fc5d0a2c38794519454053170a5eef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 00:53:51 +0000 Subject: [PATCH 44/48] deps: bump golang.org/x/crypto from 0.12.0 to 0.13.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0. - [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 86c8f296..334f7ff8 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/jeremmfr/go-netconf v0.4.13 github.com/jeremmfr/go-utils v0.10.0 github.com/jeremmfr/junosdecode v1.1.1 - golang.org/x/crypto v0.12.0 + golang.org/x/crypto v0.13.0 ) require ( @@ -55,8 +55,8 @@ require ( github.com/zclconf/go-cty v1.13.2 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.11.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.1 // indirect diff --git a/go.sum b/go.sum index a43ba7a1..cf08c6d0 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,9 @@ github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0 github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= @@ -168,22 +169,24 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 6c265efb1f3fbe680487abaa2e473c4f3462a918 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 11 Sep 2023 09:14:49 +0200 Subject: [PATCH 45/48] r/*: fix minor typo space prefix on argument description in schema --- internal/providerfwk/resource_interface_logical.go | 2 +- internal/providerfwk/resource_policyoptions_policy_statement.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/providerfwk/resource_interface_logical.go b/internal/providerfwk/resource_interface_logical.go index 5bb9132c..a0032fbd 100644 --- a/internal/providerfwk/resource_interface_logical.go +++ b/internal/providerfwk/resource_interface_logical.go @@ -626,7 +626,7 @@ func (rsc *interfaceLogical) Schema( }, Blocks: map[string]schema.Block{ "address": schema.ListNestedBlock{ - Description: " For each IPv6 address to declare.", + Description: "For each IPv6 address to declare.", NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ "cidr_ip": schema.StringAttribute{ diff --git a/internal/providerfwk/resource_policyoptions_policy_statement.go b/internal/providerfwk/resource_policyoptions_policy_statement.go index 610173f8..c85bb132 100644 --- a/internal/providerfwk/resource_policyoptions_policy_statement.go +++ b/internal/providerfwk/resource_policyoptions_policy_statement.go @@ -823,7 +823,7 @@ func (rsc *policyoptionsPolicyStatement) schemaThenBlocks() map[string]schema.Bl "action": schema.StringAttribute{ Required: false, // true when SingleNestedBlock is specified Optional: true, - Description: " Action on local-preference.", + Description: "Action on local-preference.", Validators: []validator.String{ stringvalidator.OneOf("add", "subtract", "none"), }, From 9c0fb329ec24ab6a35608b3b2af1a17c4f278378 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 11 Sep 2023 09:20:38 +0200 Subject: [PATCH 46/48] r&d/*: fix minor typo space prefix on argument description in schema --- internal/providerfwk/data_source_applications.go | 2 +- internal/providerfwk/data_source_interfaces_physical_present.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/providerfwk/data_source_applications.go b/internal/providerfwk/data_source_applications.go index 22cbb1a4..18e0b5a5 100644 --- a/internal/providerfwk/data_source_applications.go +++ b/internal/providerfwk/data_source_applications.go @@ -161,7 +161,7 @@ func (dsc *applicationsDataSource) Schema( }, "protocol": schema.StringAttribute{ Optional: true, - Description: " Match IP protocol type.", + Description: "Match IP protocol type.", }, "rpc_program_number": schema.StringAttribute{ Optional: true, diff --git a/internal/providerfwk/data_source_interfaces_physical_present.go b/internal/providerfwk/data_source_interfaces_physical_present.go index 572754e3..81a160fe 100644 --- a/internal/providerfwk/data_source_interfaces_physical_present.go +++ b/internal/providerfwk/data_source_interfaces_physical_present.go @@ -74,7 +74,7 @@ func (dsc *interfacesPhysicalPresentDataSource) Schema( }, "match_name": schema.StringAttribute{ Optional: true, - Description: " A regexp to apply filter on name.", + Description: "A regexp to apply filter on name.", Validators: []validator.String{ tfvalidator.StringRegex(), }, From 916b4f0032e2895b4fff540775f882375ff3db59 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 13 Sep 2023 09:23:09 +0200 Subject: [PATCH 47/48] r/firewall_policer: testacc: remove invalid check --- internal/providerfwk/resource_firewall_policer_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/providerfwk/resource_firewall_policer_test.go b/internal/providerfwk/resource_firewall_policer_test.go index 7bfad4eb..bd0e3c99 100644 --- a/internal/providerfwk/resource_firewall_policer_test.go +++ b/internal/providerfwk/resource_firewall_policer_test.go @@ -35,8 +35,6 @@ func TestAccJunosFirewallPolicer_basic(t *testing.T) { "then.forwarding_class", "best-effort"), resource.TestCheckResourceAttr("junos_firewall_policer.testacc_fwPolic", "then.loss_priority", "high"), - resource.TestCheckResourceAttr("junos_firewall_policer.testacc_fwPolic", - "then.out_of_profile", "true"), ), }, { From ee6d28d5cac945435495a9455e1ee97c8e9b14b1 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 13 Sep 2023 09:40:23 +0200 Subject: [PATCH 48/48] Release v2.2.0 --- .changes/go-1.21.md | 4 ---- .changes/issue-521.md | 5 ---- .changes/route-with-fwk.md | 14 ----------- .changes/security-nat-with-fwk.md | 24 ------------------- .github/workflows/releases.yml | 2 +- CHANGELOG.md | 40 +++++++++++++++++++++++++++++++ 6 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 .changes/go-1.21.md delete mode 100644 .changes/issue-521.md delete mode 100644 .changes/route-with-fwk.md delete mode 100644 .changes/security-nat-with-fwk.md diff --git a/.changes/go-1.21.md b/.changes/go-1.21.md deleted file mode 100644 index 6fe2502d..00000000 --- a/.changes/go-1.21.md +++ /dev/null @@ -1,4 +0,0 @@ - -ENHANCEMENTS: - -* release now with golang 1.21 diff --git a/.changes/issue-521.md b/.changes/issue-521.md deleted file mode 100644 index e6c9b733..00000000 --- a/.changes/issue-521.md +++ /dev/null @@ -1,5 +0,0 @@ - -ENHANCEMENTS: - -* **resource/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` arguments (Fix [#521](https://github.com/jeremmfr/terraform-provider-junos/issues/521)) -* **data-source/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` attributes diff --git a/.changes/route-with-fwk.md b/.changes/route-with-fwk.md deleted file mode 100644 index 6362eafd..00000000 --- a/.changes/route-with-fwk.md +++ /dev/null @@ -1,14 +0,0 @@ - -ENHANCEMENTS: - -* **resource/junos_aggregate_route**: 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 -* **resource/junos_generate_route**: 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 -* **resource/junos_static_route**: 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 -* **data-source/junos_routes**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) diff --git a/.changes/security-nat-with-fwk.md b/.changes/security-nat-with-fwk.md deleted file mode 100644 index 7e6a73e9..00000000 --- a/.changes/security-nat-with-fwk.md +++ /dev/null @@ -1,24 +0,0 @@ - -ENHANCEMENTS: - -* **resource/junos_security_nat_destination**: 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 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_security_nat_destination_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) - optional string attributes doesn't accept *empty* value -* **resource/junos_security_nat_source**: 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 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_security_nat_source_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) - optional string attributes doesn't accept *empty* value -* **resource/junos_security_nat_static**: 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 string attributes doesn't accept *empty* value - optional boolean attributes doesn't accept value *false* - the resource schema has been upgraded to have one-blocks in single mode instead of list -* **resource/junos_security_nat_static_rule**: 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 string attributes doesn't accept *empty* value - the resource schema has been upgraded to have one-blocks in single mode instead of list diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 33f18d04..dfce01fd 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -41,7 +41,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: '^1.21.0' + go-version: '^1.21.1' check-latest: true id: go - name: Show version diff --git a/CHANGELOG.md b/CHANGELOG.md index 92fb9f98..8ed27d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,46 @@ # changelog +## v2.2.0 (September 13, 2023) + +ENHANCEMENTS: + +* **resource/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` arguments (Fix [#521](https://github.com/jeremmfr/terraform-provider-junos/issues/521)) +* **data-source/junos_interface_physical**: add `trunk_non_els` and `vlan_native_non_els` attributes +* **resource/junos_aggregate_route**: 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 +* **resource/junos_generate_route**: 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 +* **resource/junos_static_route**: 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 +* **data-source/junos_routes**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +* **resource/junos_security_nat_destination**: 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 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_security_nat_destination_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional string attributes doesn't accept *empty* value +* **resource/junos_security_nat_source**: 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 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_security_nat_source_pool**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional string attributes doesn't accept *empty* value +* **resource/junos_security_nat_static**: 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 string attributes doesn't accept *empty* value + optional boolean attributes doesn't accept value *false* + the resource schema has been upgraded to have one-blocks in single mode instead of list +* **resource/junos_security_nat_static_rule**: 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 string attributes doesn't accept *empty* value + the resource schema has been upgraded to have one-blocks in single mode instead of list +* release now with golang 1.21 + ## v2.1.3 (August 30, 2023) BUG FIXES: