Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
36 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
ba5027a
fix(vpn): update LocalAsn initialization to use new() for pointer
s-inter May 20, 2026
ab4bafb
fix(vpn): use standard field naming for 'Id'
s-inter May 20, 2026
07d97b6
fix(vpn): preserve empty routes and labels in state mapping and updat…
s-inter May 20, 2026
c827000
fix(vpn): apply review suggestions
s-inter May 21, 2026
2327a5f
docs(vpn): add example for data source and remove region from resourc…
s-inter May 21, 2026
9e04072
Merge branch 'main' into si/onboard-VPN-gateway
s-inter May 21, 2026
0347849
refactor(vpn): use label helper in gateway resource
s-inter May 21, 2026
558a82a
Merge branch 'main' into si/onboard-VPN-gateway
s-inter May 21, 2026
0f8ff77
fix(vpn): fix tests
s-inter May 21, 2026
4538c49
refactor(vpn): remove unnecessary availability zones check in payload…
s-inter May 21, 2026
a12d990
feat(vpn): add validation for BGP routing type configuration
s-inter May 21, 2026
ea20b42
refactor(vpn): update descriptions for schema
s-inter May 22, 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.9.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.5.1
github.com/teambition/rrule-go v1.8.2
golang.org/x/mod v0.35.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,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.5.1 h1:qmfgch4YCMMstSgzDysCLhvaAhHpTf+EPy0wUUmxN70=
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.5.1/go.mod h1:zcCQlA79aWlv2Voc34EgerEFgPGxkO7CE8Ilm+nG9Yw=
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 @@ -71,6 +71,7 @@ type ProviderData struct {
ServiceEnablementCustomEndpoint string
SfsCustomEndpoint string
ServiceAccountCustomEndpoint string
VpnCustomEndpoint string
EnableBetaResources bool
Experiments []string

Expand Down
190 changes: 190 additions & 0 deletions stackit/internal/services/vpn/gateway/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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/v1beta1api"

"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)(nil)
Comment thread
s-inter marked this conversation as resolved.
Outdated
_ datasource.DataSourceWithConfigure = (*vpnGatewayDataSource)(nil)
Comment thread
s-inter marked this conversation as resolved.
Outdated
)

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

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

// Configure implements [datasource.DataSourceWithConfigure].
func (d *vpnGatewayDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
Comment thread
s-inter marked this conversation as resolved.
Outdated
if !ok {
return
}

apiClient := utils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
Comment thread
s-inter marked this conversation as resolved.
Outdated
if resp.Diagnostics.HasError() {
return
}
d.client = apiClient
d.providerData = providerData
tflog.Info(ctx, "VPN client configured")
}

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

// Schema implements [datasource.DataSource].
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"],
Required: true,
Comment thread
s-inter marked this conversation as resolved.
Outdated
},
"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,
},
},
}
}

// Read implements [datasource.DataSource].
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.GetVPNGateway(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
}

// Set state
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,
})
}
74 changes: 74 additions & 0 deletions stackit/internal/services/vpn/gateway/datasource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package gateway

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1beta1api"
)

func TestDataSourceMapFields(t *testing.T) {
tests := []struct {
description string
input *vpn.GatewayResponse
expected Model
isValid bool
}{
{
"basic_gateway",
&vpn.GatewayResponse{
Id: utils.Ptr("gateway-id"),
DisplayName: "test-gateway",
PlanId: "p500",
RoutingType: vpn.ROUTINGTYPE_ROUTE_BASED,
AvailabilityZones: vpn.GatewayAvailabilityZones{
Tunnel1: "eu01-1",
Tunnel2: "eu01-2",
},
State: utils.Ptr(vpn.GATEWAYSTATUS_READY),
},
Model{
GatewayID: types.StringValue("gateway-id"),
DisplayName: types.StringValue("test-gateway"),
PlanID: types.StringValue("p500"),
RoutingType: types.StringValue("ROUTE_BASED"),
AvailabilityZones: &AvailabilityZonesModel{
Tunnel1: types.StringValue("eu01-1"),
Tunnel2: types.StringValue("eu01-2"),
},
State: types.StringValue("READY"),
},
true,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
var model Model
model.ProjectID = types.StringValue("test-project")
model.Region = types.StringValue("eu01")

err := mapFields(context.Background(), tt.input, &model, "eu01")

if !tt.isValid && err == nil {
t.Fatalf("expected error, got none")
}
if tt.isValid && err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !tt.isValid {
return
}

if diff := cmp.Diff(model.GatewayID, tt.expected.GatewayID); diff != "" {
Comment thread
s-inter marked this conversation as resolved.
Outdated
t.Fatalf("GatewayID mismatch (-got +want):\n%s", diff)
}
if diff := cmp.Diff(model.DisplayName, tt.expected.DisplayName); diff != "" {
t.Fatalf("DisplayName mismatch (-got +want):\n%s", diff)
}
})
}
}
Loading
Loading