Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/data-sources/vpn_gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ data "stackit_vpn_gateway" "example" {
- `display_name` (String) A user-friendly name for the VPN gateway.
- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`gateway_id`".
- `labels` (Map of String) Map of custom labels (key-value string pairs).
- `network_config` (Attributes) Network configuration for the VPN gateway. (see [below for nested schema](#nestedatt--network_config))
- `plan_id` (String) The service plan identifier (e.g. `p500`). For guidance on finding available plans, see [List available service plans](https://docs.stackit.cloud/products/network/connectivity-hybrid-multi-cloud/vpn/getting-started/gateway-create/#list-available-service-plans).
- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.
- `routing_type` (String) Routing architecture. Possible values are: `POLICY_BASED`, `ROUTE_BASED`, `BGP_ROUTE_BASED`.
Expand All @@ -54,3 +55,12 @@ Read-Only:

- `local_asn` (Number) Local ASN for BGP (private ASN range, 64512-4294967294).
- `override_advertised_routes` (List of String) List of IPv4 CIDRs to advertise via BGP. If omitted, SNA network ranges are advertised.


<a id="nestedatt--network_config"></a>
### Nested Schema for `network_config`

Read-Only:

- `predefined_network_prefix` (String) The IPv4 network prefix (CIDR notation) allocated for the VPN gateway. Must have a prefix length of /28 or larger. Cannot be changed after the gateway is created.
- `routing_table_id` (String) Custom routing table ID for the VPN gateway. If omitted, a default routing table is assigned.
10 changes: 10 additions & 0 deletions docs/resources/vpn_gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ resource "stackit_vpn_gateway" "example" {

- `bgp` (Attributes) BGP configuration. Only applicable when routing_type is BGP_ROUTE_BASED. (see [below for nested schema](#nestedatt--bgp))
- `labels` (Map of String) Map of custom labels (key-value string pairs).
- `network_config` (Attributes) Network configuration for the VPN gateway. (see [below for nested schema](#nestedatt--network_config))
- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.

### Read-Only
Expand Down Expand Up @@ -68,6 +69,15 @@ Optional:

- `override_advertised_routes` (List of String) List of IPv4 CIDRs to advertise via BGP. If omitted, SNA network ranges are advertised.


<a id="nestedatt--network_config"></a>
### Nested Schema for `network_config`

Optional:

- `predefined_network_prefix` (String) The IPv4 network prefix (CIDR notation) allocated for the VPN gateway. Must have a prefix length of /28 or larger. Cannot be changed after the gateway is created.
- `routing_table_id` (String) Custom routing table ID for the VPN gateway. If omitted, a default routing table is assigned.

## Import

Import is supported using the following syntax:
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ require (
github.com/hashicorp/terraform-plugin-log v0.11.0
github.com/hashicorp/terraform-plugin-testing v1.16.0
github.com/stackitcloud/stackit-sdk-go/core v0.27.0
github.com/stackitcloud/stackit-sdk-go/experimental v0.2.0
github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.2
github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.13.4
github.com/stackitcloud/stackit-sdk-go/services/automation v0.1.1
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/stackitcloud/stackit-sdk-go/core v0.27.0 h1:7lc6qStcFDFf8zHP4ORPa9joybw+Sa2LtB6BVjKQ+ek=
github.com/stackitcloud/stackit-sdk-go/core v0.27.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
github.com/stackitcloud/stackit-sdk-go/experimental v0.2.0 h1:xRgrDL0jZ9otmF+tgSJQEcMc8b7WUJJ9UdFtX9GBQSY=
github.com/stackitcloud/stackit-sdk-go/experimental v0.2.0/go.mod h1:ebvgQJYWApj5Ktk3QfPzvDpKdghrtZcKMpWA7R3NvPs=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.2 h1:OAMCll/+6FTB65+H+BVyqJDrELUGdkMXggnveBl1O7I=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.2/go.mod h1:zEx3JFbwg1VLB9uKa2P4BA2DdypghX1FT8mbJU+msjE=
github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.13.4 h1:KNEcAPj66Asb00V7f6v3Ku2d6ybP8PL1IwFQubUuxc0=
Expand Down
14 changes: 14 additions & 0 deletions stackit/internal/services/vpn/gateway/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ func (d *vpnGatewayDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
Computed: true,
ElementType: types.StringType,
},
"network_config": schema.SingleNestedAttribute{
Description: schemaDescriptions["network_config"],
Computed: true,
Attributes: map[string]schema.Attribute{
"predefined_network_prefix": schema.StringAttribute{
Description: schemaDescriptions["network_config_predefined_network_prefix"],
Computed: true,
},
"routing_table_id": schema.StringAttribute{
Description: schemaDescriptions["network_config_routing_table_id"],
Computed: true,
},
},
},
},
}
}
Expand Down
117 changes: 115 additions & 2 deletions stackit/internal/services/vpn/gateway/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"regexp"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
Expand All @@ -20,6 +21,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api"
Expand All @@ -34,6 +36,11 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)

const (
updateGatewayAttempts = 6
updateGatewayRetryDelay = 10 * time.Second
)

var (
_ resource.Resource = &gatewayResource{}
_ resource.ResourceWithConfigure = &gatewayResource{}
Expand All @@ -54,6 +61,16 @@ type BGPGatewayConfigModel struct {
OverrideAdvertisedRoutes types.List `tfsdk:"override_advertised_routes"`
}

type NetworkConfigModel struct {
PredefinedNetworkPrefix types.String `tfsdk:"predefined_network_prefix"`
RoutingTableId types.String `tfsdk:"routing_table_id"`
}

var networkConfigTypes = map[string]attr.Type{
"predefined_network_prefix": basetypes.StringType{},
"routing_table_id": basetypes.StringType{},
}

type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
GatewayId types.String `tfsdk:"gateway_id"`
Expand All @@ -64,6 +81,7 @@ type Model struct {
RoutingType types.String `tfsdk:"routing_type"`
AvailabilityZones *AvailabilityZonesModel `tfsdk:"availability_zones"`
Bgp *BGPGatewayConfigModel `tfsdk:"bgp"`
NetworkConfig types.Object `tfsdk:"network_config"`
Labels types.Map `tfsdk:"labels"`
}

Expand All @@ -81,7 +99,10 @@ var schemaDescriptions = map[string]string{
"bgp": fmt.Sprintf("BGP configuration. Only applicable when routing_type is %s.", vpn.ROUTINGTYPE_BGP_ROUTE_BASED),
"bgp_local_asn": "Local ASN for BGP (private ASN range, 64512-4294967294).",
"bgp_override_advertised_routes": "List of IPv4 CIDRs to advertise via BGP. If omitted, SNA network ranges are advertised.",
"labels": "Map of custom labels (key-value string pairs).",
"network_config": "Network configuration for the VPN gateway.",
"network_config_predefined_network_prefix": "The IPv4 network prefix (CIDR notation) allocated for the VPN gateway. Must have a prefix length of /28 or larger. Cannot be changed after the gateway is created.",
"network_config_routing_table_id": "Custom routing table ID for the VPN gateway. If omitted, a default routing table is assigned.",
"labels": "Map of custom labels (key-value string pairs).",
}

type gatewayResource struct {
Expand Down Expand Up @@ -215,6 +236,34 @@ func (r *gatewayResource) Schema(_ context.Context, _ resource.SchemaRequest, re
},
},
},
"network_config": schema.SingleNestedAttribute{
Description: schemaDescriptions["network_config"],
Optional: true,
Attributes: map[string]schema.Attribute{
"predefined_network_prefix": schema.StringAttribute{
Description: schemaDescriptions["network_config_predefined_network_prefix"],
Optional: true,
Validators: []validator.String{
validate.CIDR(),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"routing_table_id": schema.StringAttribute{
Description: schemaDescriptions["network_config_routing_table_id"],
Optional: true,
// Computed: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented code

Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
},
"labels": schema.MapAttribute{
Description: schemaDescriptions["labels"],
Optional: true,
Expand Down Expand Up @@ -431,7 +480,13 @@ func (r *gatewayResource) Update(ctx context.Context, req resource.UpdateRequest
return
}

_, err = r.client.DefaultAPI.UpdateGateway(ctx, projectId, region, gatewayId).UpdateGatewayPayload(*payload).Execute()
retryConfig := tfutils.RetryConfig{
Attempts: updateGatewayAttempts,
Delay: updateGatewayRetryDelay,
RetryStatusCodes: []int{http.StatusConflict},
}

_, err = tfutils.RetryRequest(ctx, r.client.DefaultAPI.UpdateGateway(ctx, projectId, region, gatewayId).UpdateGatewayPayload(*payload).Execute, retryConfig)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating VPN gateway", err.Error())
return
Expand Down Expand Up @@ -525,6 +580,14 @@ func toCreatePayload(ctx context.Context, model *Model) (*vpn.CreateGatewayPaylo
payload.Bgp = bgpConfig
}

if !tfutils.IsUndefined(model.NetworkConfig) {
networkConfig, err := getNetworkConfigPayload(ctx, model)
if err != nil {
return nil, err
}
payload.NetworkConfig = &networkConfig
}

labels, err := tfutils.LabelsToPayload(ctx, model.Labels)
if err != nil {
return nil, err
Expand Down Expand Up @@ -566,6 +629,14 @@ func toUpdatePayload(ctx context.Context, model *Model) (*vpn.UpdateGatewayPaylo
payload.Bgp = bgpConfig
}

if !tfutils.IsUndefined(model.NetworkConfig) {
networkConfig, err := getNetworkConfigPayload(ctx, model)
if err != nil {
return nil, err
}
payload.NetworkConfig = &networkConfig
}

labels, err := tfutils.LabelsToPayload(ctx, model.Labels)
if err != nil {
return nil, err
Expand All @@ -575,6 +646,25 @@ func toUpdatePayload(ctx context.Context, model *Model) (*vpn.UpdateGatewayPaylo
return payload, nil
}

func getNetworkConfigPayload(ctx context.Context, model *Model) (vpn.NetworkConfig, error) {
var networkConfigModel NetworkConfigModel
diags := model.NetworkConfig.As(ctx, &networkConfigModel, basetypes.ObjectAsOptions{})
if diags.HasError() {
return vpn.NetworkConfig{}, core.DiagsToError(diags)
}
networkConfig := vpn.NetworkConfig{}

if !tfutils.IsUndefined(networkConfigModel.PredefinedNetworkPrefix) {
networkConfig.PredefinedNetworkPrefix = networkConfigModel.PredefinedNetworkPrefix.ValueStringPointer()
}

if !tfutils.IsUndefined(networkConfigModel.RoutingTableId) {
networkConfig.RoutingTableId = networkConfigModel.RoutingTableId.ValueStringPointer()
}

return networkConfig, nil
}

func mapFields(ctx context.Context, gateway *vpn.GatewayResponse, model *Model, region string) error {
if gateway == nil {
return fmt.Errorf("response input is nil")
Expand Down Expand Up @@ -617,6 +707,29 @@ func mapFields(ctx context.Context, gateway *vpn.GatewayResponse, model *Model,
model.Bgp = bgpModel
}

if gateway.NetworkConfig == nil {
model.NetworkConfig = types.ObjectNull(networkConfigTypes)
} else {
predefinedNetworkPrefix := types.StringNull()
if gateway.NetworkConfig.PredefinedNetworkPrefix != nil {
predefinedNetworkPrefix = types.StringValue(*gateway.NetworkConfig.PredefinedNetworkPrefix)
}

routingTableId := types.StringNull()
if gateway.NetworkConfig.RoutingTableId != nil {
routingTableId = types.StringValue(*gateway.NetworkConfig.RoutingTableId)
}

networkConfigObject, diags := types.ObjectValue(networkConfigTypes, map[string]attr.Value{
"predefined_network_prefix": predefinedNetworkPrefix,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"predefined_network_prefix": predefinedNetworkPrefix,
"predefined_network_prefix": types.StringPointerValue(gateway.NetworkConfig.PredefinedNetworkPrefix),

Same should be possible for the routing table id.

Saves you a lot of code and especially the two if conditions above.

"routing_table_id": routingTableId,
})
if diags.HasError() {
return fmt.Errorf("mapping network config: %w", core.DiagsToError(diags))
}
model.NetworkConfig = networkConfigObject
}

labels, err := tfutils.MapLabels(ctx, gateway.Labels, model.Labels)
if err != nil {
return fmt.Errorf("mapping labels: %w", err)
Expand Down
Loading
Loading