Skip to content
Merged
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
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
- **New**: STACKIT Model Experiments module wait handler added.
- `rabbitmq`:
- [v1.1.0](services/rabbitmq/CHANGELOG.md#v110)
- `v2api`:
- **Feature**: Added wait handlers
- `v2api`:
- **Feature**: Added wait handlers
- `redis`:
- [v1.1.0](services/redis/CHANGELOG.md#v110)
- `v2api`:
- **Feature**: Added wait handlers
- `ske`:
- [v1.19.0](services/ske/CHANGELOG.md#v1190)
- Package `v1api`:
Expand Down
6 changes: 2 additions & 4 deletions examples/redis/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ go 1.25
// This is not needed in production. This is only here to point the golangci linter to the local version instead of the last release on GitHub.
replace github.com/stackitcloud/stackit-sdk-go/services/redis => ../../services/redis

require (
github.com/stackitcloud/stackit-sdk-go/core v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/redis v0.30.0
)
require github.com/stackitcloud/stackit-sdk-go/services/redis v1.1.0

require (
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/stackitcloud/stackit-sdk-go/core v0.26.0 // indirect
)
43 changes: 33 additions & 10 deletions examples/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,32 @@ import (
"fmt"
"os"

"github.com/stackitcloud/stackit-sdk-go/core/config"
redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v1api"
redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api"
"github.com/stackitcloud/stackit-sdk-go/services/redis/v2api/wait"
)

func main() {
projectId := "PROJECT_ID" // the uuid of your STACKIT project
planId := "PLAN_ID"
region := "eu01"

// Create a new API client, that uses default authentication and configuration
redisClient, err := redis.NewAPIClient(
config.WithRegion("eu01"),
)
redisClient, err := redis.NewAPIClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Creating API client: %v\n", err)
os.Exit(1)
}

// Get the redis instances for your project
getInstancesResp, err := redisClient.DefaultAPI.ListInstances(context.Background(), projectId).Execute()
getInstancesResp, err := redisClient.DefaultAPI.ListInstances(context.Background(), projectId, region).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `GetInstances`: %v\n", err)
} else {
fmt.Printf("Number of instances: %v\n", len(getInstancesResp.Instances))
}

// Get the redis offerings for your project
getOfferingsResp, err := redisClient.DefaultAPI.ListOfferings(context.Background(), projectId).Execute()
getOfferingsResp, err := redisClient.DefaultAPI.ListOfferings(context.Background(), projectId, region).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `GetOfferings`: %v\n", err)
} else {
Expand All @@ -44,10 +43,34 @@ func main() {
Parameters: &redis.InstanceParameters{},
PlanId: planId,
}
createInstanceResp, err := redisClient.DefaultAPI.CreateInstance(context.Background(), projectId).CreateInstancePayload(createInstancePayload).Execute()
createInstanceResp, err := redisClient.DefaultAPI.CreateInstance(context.Background(), projectId, region).CreateInstancePayload(createInstancePayload).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `CreateInstance`: %v\n", err)
} else {
fmt.Printf("Created instance with instance id \"%s\".\n", createInstanceResp.InstanceId)
os.Exit(1)
}
fmt.Printf("Triggered creation of instance with instance id \"%s\".\n", createInstanceResp.InstanceId)

// Wait for creation of redis instance
instance, err := wait.CreateInstanceWaitHandler(context.Background(), redisClient.DefaultAPI, projectId, region, createInstanceResp.InstanceId).WaitWithContext(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Error when waiting for creation: %v\n", err)
os.Exit(1)
}
fmt.Printf("Redis instance %q has been successfully created.\n", *instance.InstanceId)

// Delete a redis instance
err = redisClient.DefaultAPI.DeleteInstance(context.Background(), projectId, region, *instance.InstanceId).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling 'DeleteInstance': %v\n", err)
os.Exit(1)
}
fmt.Printf("Deleting instance with instance id %q.\n", createInstanceResp.InstanceId)

// Wait for deletion of redis instance
_, err = wait.DeleteInstanceWaitHandler(context.Background(), redisClient.DefaultAPI, projectId, region, *instance.InstanceId).WaitWithContext(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Error when waiting for deletion: %v\n", err)
os.Exit(1)
}
fmt.Printf("Redis instance %q has been successfully deleted.\n", *instance.InstanceId)
}
3 changes: 3 additions & 0 deletions services/redis/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v1.1.0
- `v2api`: **Feature**: Added wait handlers

## v1.0.0
- **Breaking Change:** The region is no longer specified within the client configuration. Instead, the region must be passed as a parameter to any region-specific request.
- `v2api`:
Expand Down
2 changes: 1 addition & 1 deletion services/redis/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.0.0
v1.1.0
121 changes: 121 additions & 0 deletions services/redis/v2api/wait/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package wait

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

"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/core/wait"
redis "github.com/stackitcloud/stackit-sdk-go/services/redis/v2api"
)

func createOrUpdateInstanceWaitHandler(ctx context.Context, client redis.DefaultAPI, projectId, region, instanceId string) *wait.AsyncActionHandler[redis.Instance] {
waitConfig := wait.WaiterHelper[redis.Instance, redis.InstanceStatus]{
FetchInstance: client.GetInstance(ctx, projectId, region, instanceId).Execute,
GetState: func(response *redis.Instance) (redis.InstanceStatus, error) {
if response == nil {
return "", errors.New("empty response")
}
if response.Status == nil {
return "", errors.New("status is missing in response")
}
return *response.Status, nil
},
ActiveState: []redis.InstanceStatus{redis.INSTANCESTATUS_ACTIVE},
ErrorState: []redis.InstanceStatus{redis.INSTANCESTATUS_FAILED},
}

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

// CreateInstanceWaitHandler will wait for instance creation
func CreateInstanceWaitHandler(ctx context.Context, client redis.DefaultAPI, projectId, region, instanceId string) *wait.AsyncActionHandler[redis.Instance] {
return createOrUpdateInstanceWaitHandler(ctx, client, projectId, region, instanceId)
}

// PartialUpdateInstanceWaitHandler will wait for instance update
func PartialUpdateInstanceWaitHandler(ctx context.Context, client redis.DefaultAPI, projectId, region, instanceId string) *wait.AsyncActionHandler[redis.Instance] {
return createOrUpdateInstanceWaitHandler(ctx, client, projectId, region, instanceId)
}

// DeleteInstanceWaitHandler will wait for instance deletion
func DeleteInstanceWaitHandler(ctx context.Context, a redis.DefaultAPI, projectId, region, instanceId string) *wait.AsyncActionHandler[struct{}] {
handler := wait.New(func() (waitFinished bool, response *struct{}, err error) {
s, err := a.GetInstance(ctx, projectId, region, instanceId).Execute()
if err == nil {
if s.Status == nil {
return false, nil, fmt.Errorf("delete failed for instance with id %s. The response is not valid: The status is missing", instanceId)
}
if *s.Status != redis.INSTANCESTATUS_DELETING {
return false, nil, nil
}
if *s.Status == redis.INSTANCESTATUS_ACTIVE {
if strings.Contains(s.LastOperation.Description, "DeleteFailed") || strings.Contains(s.LastOperation.Description, "failed") {
return true, nil, fmt.Errorf("instance was deleted successfully but has errors: %s", s.LastOperation.Description)
}
return true, nil, nil
}
return false, nil, nil
}
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError")
}
if oapiErr.StatusCode != http.StatusGone {
return false, nil, err
}
return true, nil, nil
})
handler.SetTimeout(15 * time.Minute)
return handler
}

// CreateCredentialsWaitHandler will wait for credentials creation
func CreateCredentialsWaitHandler(ctx context.Context, a redis.DefaultAPI, projectId, region, instanceId, credentialsId string) *wait.AsyncActionHandler[redis.CredentialsResponse] {
handler := wait.New(func() (waitFinished bool, response *redis.CredentialsResponse, err error) {
s, err := a.GetCredentials(ctx, projectId, region, instanceId, credentialsId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError")
}
// If the request returns 404, the credentials have not been created yet
if oapiErr.StatusCode == http.StatusNotFound {
return false, nil, nil
}
return false, nil, err
}
if s.Id == credentialsId {
return true, s, nil
}
return false, nil, nil
})
handler.SetTimeout(1 * time.Minute)
return handler
}

// DeleteCredentialsWaitHandler will wait for credentials deletion
func DeleteCredentialsWaitHandler(ctx context.Context, a redis.DefaultAPI, projectId, region, instanceId, credentialsId string) *wait.AsyncActionHandler[struct{}] {
handler := wait.New(func() (waitFinished bool, response *struct{}, err error) {
_, err = a.GetCredentials(ctx, projectId, region, instanceId, credentialsId).Execute()
if err == nil {
return false, nil, nil
}
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError")
}
if oapiErr.StatusCode != http.StatusNotFound && oapiErr.StatusCode != http.StatusGone {
return false, nil, err
}
return true, nil, nil
})
handler.SetTimeout(1 * time.Minute)
return handler
}
Loading
Loading