Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
38faf3b
feat(vpn): add vpn to provider and core
s-inter Apr 28, 2026
c7bc13a
feat(vpn): add utils
s-inter Apr 28, 2026
84a4998
feat(vpn): add datasource for VPN gateway
s-inter Apr 30, 2026
7dbf049
feat(vpn): add resource for VPN gateway
s-inter Apr 30, 2026
ce6c205
feat(vpn): add unit tests for VPN gateway
s-inter Apr 30, 2026
907878a
feat(vpn): add test data and acceptance tests for VPN gateway
s-inter May 4, 2026
161ec48
fix(vpn): resolve linter issues - linter bypass per contribution guid…
s-inter May 4, 2026
d7c6581
fix(vpn): update gateway tests to use new() function
s-inter May 11, 2026
f9bce3e
feat(vpn): add example resource for VPN gateway
s-inter May 11, 2026
046bce8
fix(vpn): update VPN gateway data source schema and add acceptance tests
s-inter May 12, 2026
b3b1b93
feat(vpn): upgrade to v1 api version
s-inter May 13, 2026
1f5dc72
Merge branch 'main' into si/onboard-VPN-gateway
s-inter May 13, 2026
9dddb67
feat(vpn): update vpn service dependency to v0.8.0
s-inter May 13, 2026
a1d144a
feat(docs): add VPN Gateway documentation
s-inter May 13, 2026
3f3255a
Merge branch 'main' into si/onboard-VPN-gateway
s-inter May 18, 2026
89f8aa4
fix(vpn): apply review suggestions
s-inter May 18, 2026
717b089
fix(vpn): remove duplicate util function
s-inter May 18, 2026
8be876d
fix(vpn): update VPN service version to v0.9.0 and refactor client co…
s-inter May 19, 2026
a4d44e5
fix(vpn): rename to util.go (singular)
s-inter May 19, 2026
889dd7c
fix(vpn): remove redundant datasource test file (map function already…
s-inter May 19, 2026
de6c7a2
fix(vpn): refactor gateway unit tests
s-inter May 19, 2026
b1b2d6d
fix(vpn): fix acceptance test
s-inter May 19, 2026
47d8450
fix(vpn): add additional acc testing
s-inter May 20, 2026
8dc4893
Merge branch 'main' into si/onboard-VPN-gateway
s-inter May 20, 2026
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
50 changes: 50 additions & 0 deletions docs/data-sources/vpn_gateway.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_vpn_gateway Data Source - stackit"
subcategory: ""
description: |-
VPN Gateway data source schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level.
---

# stackit_vpn_gateway (Data Source)

VPN Gateway data source schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `gateway_id` (String) The server-generated UUID of the VPN gateway.
- `project_id` (String) STACKIT project ID associated with the VPN gateway.

### Read-Only

- `availability_zones` (Attributes) Availability zones for the two tunnel endpoints. (see [below for nested schema](#nestedatt--availability_zones))
- `bgp` (Attributes) BGP configuration. Only applicable when routing_type is BGP_ROUTE_BASED. (see [below for nested schema](#nestedatt--bgp))
- `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).
- `plan_id` (String) The service plan identifier (e.g. p500).
- `region` (String) STACKIT region (e.g. eu01).
- `routing_type` (String) Routing architecture: POLICY_BASED, ROUTE_BASED, or BGP_ROUTE_BASED.
- `state` (String) The current lifecycle state of the gateway (PENDING, READY, ERROR, DELETING).

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

Read-Only:

- `tunnel1` (String) Availability zone for tunnel 1.
- `tunnel2` (String) Availability zone for tunnel 2.


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

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.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,4 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `sqlserverflex_custom_endpoint` (String) Custom endpoint for the SQL Server Flex service
- `token_custom_endpoint` (String) Custom endpoint for the token API, which is used to request access tokens when using the key flow
- `use_oidc` (Boolean) Enables OIDC for Authentication. This can also be sourced from the `STACKIT_USE_OIDC` Environment Variable. Defaults to `false`.
- `vpn_custom_endpoint` (String) Custom endpoint for the VPN service
74 changes: 74 additions & 0 deletions docs/resources/vpn_gateway.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_vpn_gateway Resource - stackit"
subcategory: ""
description: |-
VPN Gateway resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level.
---

# stackit_vpn_gateway (Resource)

VPN Gateway resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level.

## Example Usage

```terraform
resource "stackit_vpn_gateway" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region = "eu01"
display_name = "example-vpn-gateway"
plan_id = "p500"
routing_type = "ROUTE_BASED"

availability_zones = {
tunnel1 = "eu01-1"
tunnel2 = "eu01-2"
}
}

# Only use the import statement, if you want to import an existing VPN gateway
import {
to = stackit_vpn_gateway.example
id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,eu01,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `availability_zones` (Attributes) Availability zones for the two tunnel endpoints. (see [below for nested schema](#nestedatt--availability_zones))
- `display_name` (String) A user-friendly name for the VPN gateway.
- `plan_id` (String) The service plan identifier (e.g. p500).
- `project_id` (String) STACKIT project ID associated with the VPN gateway.
- `routing_type` (String) Routing architecture: POLICY_BASED, ROUTE_BASED, or BGP_ROUTE_BASED.

### Optional

- `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).
- `region` (String) STACKIT region (e.g. eu01).

### Read-Only

- `gateway_id` (String) The server-generated UUID of the VPN gateway.
- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`gateway_id`".
- `state` (String) The current lifecycle state of the gateway (PENDING, READY, ERROR, DELETING).

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

Required:

- `tunnel1` (String) Availability zone for tunnel 1.
- `tunnel2` (String) Availability zone for tunnel 2.


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

Optional:

- `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.
18 changes: 18 additions & 0 deletions examples/resources/stackit_vpn_gateway/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
resource "stackit_vpn_gateway" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
region = "eu01"
display_name = "example-vpn-gateway"
plan_id = "p500"
routing_type = "ROUTE_BASED"

availability_zones = {
tunnel1 = "eu01-1"
tunnel2 = "eu01-2"
}
}

# Only use the import statement, if you want to import an existing VPN gateway
import {
to = stackit_vpn_gateway.example
id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,eu01,xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.10.0
github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.9.0
github.com/teambition/rrule-go v1.8.2
golang.org/x/mod v0.36.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0 h1:Zy3yxmHzW+ydu1nae
github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0 h1:angvO3z0TGqZtdwTDsG/tgTw9hxB76A6leUsiUXQtME=
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.9.0 h1:ZqZ0Wbkyz1rnclTTvnAKNalo0oSNWq9pyS4lO/athaQ=
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.9.0/go.mod h1:toIjQk1dhxdUFVyCWJJja0w/0nFpDid8MWX0ukQfvfo=
github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=
github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
1 change: 1 addition & 0 deletions stackit/internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type ProviderData struct {
ServiceEnablementCustomEndpoint string
SfsCustomEndpoint string
ServiceAccountCustomEndpoint string
VpnCustomEndpoint string
EnableBetaResources bool
Experiments []string

Expand Down
184 changes: 184 additions & 0 deletions stackit/internal/services/vpn/gateway/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package gateway

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"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"

"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/vpn/utils"

"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)

var (
_ datasource.DataSource = &vpnGatewayDataSource{}
_ datasource.DataSourceWithConfigure = &vpnGatewayDataSource{}
)

type vpnGatewayDataSource struct {
client *vpn.APIClient
providerData core.ProviderData
}

func NewVPNGatewayDataSource() datasource.DataSource {
return &vpnGatewayDataSource{}
}

func (d *vpnGatewayDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}

d.client = utils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "VPN client configured")
}

func (d *vpnGatewayDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpn_gateway"
}

func (d *vpnGatewayDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: fmt.Sprintf("VPN Gateway data source schema. %s", core.DatasourceRegionFallbackDocstring),
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: schemaDescriptions["id"],
Computed: true,
},
"project_id": schema.StringAttribute{
Description: schemaDescriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Description: schemaDescriptions["region"],
Computed: true,
},
"gateway_id": schema.StringAttribute{
Description: schemaDescriptions["gateway_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"display_name": schema.StringAttribute{
Description: schemaDescriptions["display_name"],
Computed: true,
},
"plan_id": schema.StringAttribute{
Description: schemaDescriptions["plan_id"],
Computed: true,
},
"routing_type": schema.StringAttribute{
Description: schemaDescriptions["routing_type"],
Computed: true,
},
"availability_zones": schema.SingleNestedAttribute{
Description: schemaDescriptions["availability_zones"],
Computed: true,
Attributes: map[string]schema.Attribute{
"tunnel1": schema.StringAttribute{
Description: "Availability zone for tunnel 1.",
Computed: true,
},
"tunnel2": schema.StringAttribute{
Description: "Availability zone for tunnel 2.",
Computed: true,
},
},
},
"bgp": schema.SingleNestedAttribute{
Description: schemaDescriptions["bgp"],
Computed: true,
Attributes: map[string]schema.Attribute{
"local_asn": schema.Int64Attribute{
Description: "Local ASN for BGP (private ASN range, 64512-4294967294).",
Computed: true,
},
"override_advertised_routes": schema.ListAttribute{
Description: "List of IPv4 CIDRs to advertise via BGP.",
Computed: true,
ElementType: types.StringType,
},
},
},
"labels": schema.MapAttribute{
Description: schemaDescriptions["labels"],
Computed: true,
ElementType: types.StringType,
},
"state": schema.StringAttribute{
Description: schemaDescriptions["state"],
Computed: true,
},
},
}
}

func (d *vpnGatewayDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

ctx = core.InitProviderContext(ctx)

projectId := model.ProjectID.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
gatewayId := model.GatewayID.ValueString()

ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "gateway_id", gatewayId)

gatewayResponse, err := d.client.DefaultAPI.GetGateway(ctx, projectId, vpn.Region(region), gatewayId).Execute()
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading VPN gateway", fmt.Sprintf("Calling API: %v", err))
return
}
ctx = core.LogResponse(ctx)

err = mapFields(ctx, gatewayResponse, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading VPN gateway", fmt.Sprintf("Processing response: %v", err))
return
}

diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "VPN gateway read", map[string]any{
"gateway_id": gatewayId,
})
}
Loading
Loading