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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ bin/
*.vscode/

go.work.sum

# OS generated files
.DS_Store
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@
- **Dependencies:** Bump STACKIT SDK core module to `v0.26.0`
- [v0.6.0](services/vpn/CHANGELOG.md#v060)
- **Feature:** Added `_UNKNOWN_DEFAULT_OPEN_API` fallback value to all enums to handle unknown API values gracefully.
- [v0.7.0](services/vpn/CHANGELOG.md#v070)
- **Feature:** Add new wait handlers for gateway creation, update (`CreateOrUpdateGatewayWaitHandler`), and gateway deletion (`DeleteGatewayWaitHandler`)


## Release (2026-04-07)
Expand Down
3 changes: 3 additions & 0 deletions services/vpn/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v0.7.0
- **Feature:** Add new wait handlers for gateway creation, update (`CreateOrUpdateGatewayWaitHandler`), and gateway deletion (`DeleteGatewayWaitHandler`)

## v0.6.0
- **Feature:** Added `_UNKNOWN_DEFAULT_OPEN_API` fallback value to all enums to handle unknown API values gracefully.

Expand Down
2 changes: 1 addition & 1 deletion services/vpn/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.6.0
v0.7.0
5 changes: 4 additions & 1 deletion services/vpn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/stackitcloud/stackit-sdk-go/services/vpn

go 1.25

require github.com/stackitcloud/stackit-sdk-go/core v0.26.0
require (
github.com/google/go-cmp v0.7.0
github.com/stackitcloud/stackit-sdk-go/core v0.26.0
)

require (
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
Expand Down
54 changes: 54 additions & 0 deletions services/vpn/v1beta1api/wait/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package wait

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

"github.com/stackitcloud/stackit-sdk-go/core/wait"
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1beta1api"
)

func CreateOrUpdateGatewayWaitHandler(ctx context.Context, a vpn.DefaultAPI, projectId string, region vpn.Region, gatewayId string) *wait.AsyncActionHandler[vpn.GatewayResponse] {
waitConfig := wait.WaiterHelper[vpn.GatewayResponse, vpn.GatewayStatus]{
FetchInstance: a.GetVPNGateway(ctx, projectId, region, gatewayId).Execute,
GetState: func(resp *vpn.GatewayResponse) (vpn.GatewayStatus, error) {
if resp == nil {
return "", errors.New("could not get gateway status: response is nil")
}
if resp.State == nil {
return "", errors.New("could not get gateway status: state is nil")
}
return *resp.State, nil
},
ActiveState: []vpn.GatewayStatus{vpn.GATEWAYSTATUS_READY},
ErrorState: []vpn.GatewayStatus{vpn.GATEWAYSTATUS_ERROR, vpn.GATEWAYSTATUS_DELETING},
}

handler := wait.New(waitConfig.Wait())
handler.SetTimeout(45 * time.Minute)
return handler
}

func DeleteGatewayWaitHandler(ctx context.Context, a vpn.DefaultAPI, projectId string, region vpn.Region, gatewayId string) *wait.AsyncActionHandler[vpn.GatewayResponse] {
waitConfig := wait.WaiterHelper[vpn.GatewayResponse, vpn.GatewayStatus]{
FetchInstance: a.GetVPNGateway(ctx, projectId, region, gatewayId).Execute,
GetState: func(resp *vpn.GatewayResponse) (vpn.GatewayStatus, error) {
if resp == nil {
return "", errors.New("could not get gateway status: response is nil")
}
if resp.State == nil {
return "", errors.New("could not get gateway status: state is nil")
}
return *resp.State, nil
},
ErrorState: []vpn.GatewayStatus{vpn.GATEWAYSTATUS_ERROR},
// used default so technically not needed to be set:
DeleteHttpErrorStatusCodes: []int{http.StatusForbidden, http.StatusNotFound, http.StatusGone},
}

handler := wait.New(waitConfig.Wait())
handler.SetTimeout(20 * time.Minute)
return handler
}
290 changes: 290 additions & 0 deletions services/vpn/v1beta1api/wait/wait_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
package wait

import (
"context"
"net/http"
"testing"
"testing/synctest"

"github.com/google/go-cmp/cmp"

"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1beta1api"
)

type mockSettings struct {
getFails bool
getNotFound bool
getForbidden bool
getGone bool
gatewayState vpn.GatewayStatus
gatewayId string
}

func newAPIMock(settings []mockSettings) vpn.DefaultAPI {
count := 0
return &vpn.DefaultAPIServiceMock{
GetVPNGatewayExecuteMock: utils.Ptr(func(_ vpn.ApiGetVPNGatewayRequest) (*vpn.GatewayResponse, error) {
setting := settings[count%len(settings)]
count++

if setting.getFails {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: http.StatusInternalServerError,
}
}

if setting.getNotFound {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: http.StatusNotFound,
}
}

if setting.getForbidden {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: http.StatusForbidden,
}
}

if setting.getGone {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: http.StatusGone,
}
}

return &vpn.GatewayResponse{
Id: &setting.gatewayId,
State: &setting.gatewayState,
}, nil
}),
}
}

func TestCreateOrUpdateGatewayWaitHandler(t *testing.T) {
tests := []struct {
desc string
mockSettings []mockSettings
wantGatewayState vpn.GatewayStatus
wantErr bool
wantResp bool
}{
{
desc: "create_succeeded",
mockSettings: []mockSettings{
{gatewayState: vpn.GATEWAYSTATUS_READY, gatewayId: "gw-1"},
},
wantGatewayState: vpn.GATEWAYSTATUS_READY,
wantErr: false,
wantResp: true,
},
{
desc: "pending_multiple_times",
mockSettings: []mockSettings{
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_READY,
gatewayId: "gw-1",
},
},
wantGatewayState: vpn.GATEWAYSTATUS_READY,
wantErr: false,
wantResp: true,
},
{
desc: "error_state",
mockSettings: []mockSettings{
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_ERROR,
gatewayId: "gw-1",
},
},
wantErr: true,
wantResp: false,
},
{
desc: "deleting_state",
mockSettings: []mockSettings{
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_DELETING,
gatewayId: "gw-1",
},
},
wantErr: true,
wantResp: false,
},
{
desc: "get_fails",
mockSettings: []mockSettings{
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_PENDING,
gatewayId: "gw-1",
},
{
getFails: true,
},
},
wantErr: true,
wantResp: false,
},
{
desc: "unknown_state",
mockSettings: []mockSettings{
{
gatewayState: vpn.GatewayStatus("UNKNOWN_STATE"),
gatewayId: "gw-1",
},
},
wantErr: true,
wantResp: false,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
apiClient := newAPIMock(tt.mockSettings)

var wantRes *vpn.GatewayResponse
if tt.wantResp {
wantRes = &vpn.GatewayResponse{
Id: utils.Ptr("gw-1"),
State: utils.Ptr(tt.wantGatewayState),
}
}

handler := CreateOrUpdateGatewayWaitHandler(context.Background(), apiClient, "pid", vpn.REGION_EU01, "gw-1")

gotRes, err := handler.WaitWithContext(context.Background())

if (err != nil) != tt.wantErr {
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && !cmp.Equal(gotRes, wantRes) {
t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes)
}
})
})
}
}

func TestDeleteGatewayWaitHandler(t *testing.T) {
tests := []struct {
desc string
mockSettings []mockSettings
wantErr bool
}{
{
desc: "delete_succeeded",
mockSettings: []mockSettings{
{
gatewayState: vpn.GatewayStatus(""),
getFails: false,
getNotFound: true,
},
},
wantErr: false,
},
{
desc: "delete_succeeded_forbidden",
mockSettings: []mockSettings{
{
gatewayState: vpn.GatewayStatus(""),
getForbidden: true,
},
},
wantErr: false,
},
{
desc: "delete_succeeded_gone",
mockSettings: []mockSettings{
{
gatewayState: vpn.GatewayStatus(""),
getGone: true,
},
},
wantErr: false,
},
{
desc: "delete_pending",
mockSettings: []mockSettings{
{
getFails: false,
getNotFound: false,
gatewayState: vpn.GATEWAYSTATUS_DELETING,
gatewayId: "gw-1",
},
{
getFails: false,
getNotFound: false,
gatewayState: vpn.GATEWAYSTATUS_DELETING,
gatewayId: "gw-1",
},
{
getFails: false,
getNotFound: true,
gatewayState: vpn.GatewayStatus(""),
},
},
wantErr: false,
},
{
desc: "error_state",
mockSettings: []mockSettings{
{
gatewayState: vpn.GATEWAYSTATUS_DELETING,
gatewayId: "gw-1",
},
{
gatewayState: vpn.GATEWAYSTATUS_ERROR,
gatewayId: "gw-1",
},
},
wantErr: true,
},
{
desc: "timeout",
mockSettings: []mockSettings{
{
getFails: false,
gatewayState: vpn.GatewayStatus("UNKNOWN_STATE"),
gatewayId: "gw-1",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
apiClient := newAPIMock(tt.mockSettings)

handler := DeleteGatewayWaitHandler(context.Background(), apiClient, "pid", vpn.REGION_EU01, "gw-1")

_, err := handler.WaitWithContext(context.Background())

if (err != nil) != tt.wantErr {
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
}
})
})
}
}
Loading