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
5 changes: 3 additions & 2 deletions pkg/stackit/client/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import (
sdkWait "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api/wait"
)

func withResponseID[T any](ctx context.Context, call func(context.Context) (T, error)) (T, error) {
func execute[T any](ctx context.Context, call func(context.Context) (T, error)) (T, error) {
var httpResp *http.Response
ctx = runtime.WithCaptureHTTPResponse(ctx, &httpResp)

resp, err := call(ctx)
if err != nil {
var zero T
err = stackiterrors.WrapError(err, "X-Trace-Id", runtime.GetTraceId(ctx))
if httpResp != nil {
reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader)
return zero, stackiterrors.WrapErrorWithResponseID(err, reqID)
err = stackiterrors.WrapErrorWithResponseID(err, reqID)
}
return zero, err
}
Expand Down
27 changes: 27 additions & 0 deletions pkg/stackit/client/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package client

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

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config"
)

var _ = Describe("execute", func() {
It("wraps API errors with trace and request IDs", func() {
_, err := execute(context.Background(), func(ctx context.Context) (int, error) {
response, ok := ctx.Value(sdkconfig.ContextHTTPResponse).(**http.Response)
Expect(ok).To(BeTrue())
*response = &http.Response{Header: http.Header{
"X-Trace-Id": {"trace-123"},
"X-Request-Id": {"request-456"},
}}
return 0, errors.New("api error")
})

Expect(err).To(MatchError("[X-Request-Id:request-456]: [X-Trace-Id:trace-123]: api error"))
})
})
40 changes: 20 additions & 20 deletions pkg/stackit/client/iaas.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,19 @@ func NewIaaSClient(region, projectID string, options []sdkconfig.ConfigurationOp
}

func (i *iaasClient) GetServer(ctx context.Context, serverID string) (*iaas.Server, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Server, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Server, error) {
return i.Client.GetServer(ctx, i.projectID, i.region, serverID).Execute()
})
}

func (i *iaasClient) GetServerWithDetails(ctx context.Context, serverID string) (*iaas.Server, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Server, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Server, error) {
return i.Client.GetServer(ctx, i.projectID, i.region, serverID).Details(true).Execute()
})
}

func (i *iaasClient) ListServers(ctx context.Context) (*[]iaas.Server, error) {
return withResponseID(ctx, func(ctx context.Context) (*[]iaas.Server, error) {
return execute(ctx, func(ctx context.Context) (*[]iaas.Server, error) {
resp, err := i.Client.ListServers(ctx, i.projectID, i.region).Details(true).Execute()
if err != nil {
return nil, err
Expand All @@ -133,7 +133,7 @@ func (i *iaasClient) ListServers(ctx context.Context) (*[]iaas.Server, error) {

//nolint:gocritic // Payload is passed by value to match the shared IaaSClient interface.
func (i *iaasClient) CreateSnapshot(ctx context.Context, payload iaas.CreateSnapshotPayload) (*iaas.Snapshot, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
return i.Client.
CreateSnapshot(ctx, i.projectID, i.region).
CreateSnapshotPayload(payload).
Expand All @@ -142,7 +142,7 @@ func (i *iaasClient) CreateSnapshot(ctx context.Context, payload iaas.CreateSnap
}

func (i *iaasClient) ListSnapshots(ctx context.Context, filters map[string]string) ([]iaas.Snapshot, string, error) {
resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.SnapshotListResponse, error) {
resp, err := execute(ctx, func(ctx context.Context) (*iaas.SnapshotListResponse, error) {
return i.Client.ListSnapshotsInProject(ctx, i.projectID, i.region).Execute()
})
if err != nil {
Expand All @@ -155,14 +155,14 @@ func (i *iaasClient) ListSnapshots(ctx context.Context, filters map[string]strin
}

func (i *iaasClient) DeleteSnapshot(ctx context.Context, snapshotID string) error {
_, err := withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err := execute(ctx, func(ctx context.Context) (any, error) {
return nil, i.Client.DeleteSnapshot(ctx, i.projectID, i.region, snapshotID).Execute()
})
return err
}

func (i *iaasClient) GetSnapshot(ctx context.Context, snapshotID string) (*iaas.Snapshot, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
return i.Client.GetSnapshot(ctx, i.projectID, i.region, snapshotID).Execute()
})
}
Expand Down Expand Up @@ -197,7 +197,7 @@ func (i *iaasClient) WaitSnapshotReady(ctx context.Context, snapshotID string) (
}

func (i *iaasClient) snapshotIsReady(ctx context.Context, snapshotID string) (bool, error) {
snapshot, err := withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
snapshot, err := execute(ctx, func(ctx context.Context) (*iaas.Snapshot, error) {
return i.Client.GetSnapshot(ctx, i.projectID, i.region, snapshotID).Execute()
})
if err != nil {
Expand All @@ -213,7 +213,7 @@ func (i *iaasClient) CreateBackup(ctx context.Context, name, volID, snapshotID s
return nil, err
}

return withResponseID(ctx, func(ctx context.Context) (*iaas.Backup, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Backup, error) {
return i.Client.
CreateBackup(ctx, i.projectID, i.region).
CreateBackupPayload(payload).
Expand Down Expand Up @@ -257,7 +257,7 @@ func BuildCreateBackupPayload(name, volID, snapshotID string, tags map[string]st
}

func (i *iaasClient) ListBackups(ctx context.Context, filters map[string]string) ([]iaas.Backup, error) {
resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.BackupListResponse, error) {
resp, err := execute(ctx, func(ctx context.Context) (*iaas.BackupListResponse, error) {
return i.Client.ListBackups(ctx, i.projectID, i.region).Execute()
})
if err != nil {
Expand All @@ -270,14 +270,14 @@ func (i *iaasClient) ListBackups(ctx context.Context, filters map[string]string)
}

func (i *iaasClient) DeleteBackup(ctx context.Context, backupID string) error {
_, err := withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err := execute(ctx, func(ctx context.Context) (any, error) {
return nil, i.Client.DeleteBackup(ctx, i.projectID, i.region, backupID).Execute()
})
return err
}

func (i *iaasClient) GetBackup(ctx context.Context, backupID string) (*iaas.Backup, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Backup, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Backup, error) {
return i.Client.GetBackup(ctx, i.projectID, i.region, backupID).Execute()
})
}
Expand Down Expand Up @@ -341,7 +341,7 @@ func (i *iaasClient) backupIsReady(ctx context.Context, backupID string) (bool,
func (i *iaasClient) CreateVolume(ctx context.Context, payload iaas.CreateVolumePayload) (*iaas.Volume, error) {
payload.Description = new(VolumeDescription)

return withResponseID(ctx, func(ctx context.Context) (*iaas.Volume, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Volume, error) {
return i.Client.CreateVolume(ctx, i.projectID, i.region).CreateVolumePayload(payload).Execute()
})
}
Expand All @@ -355,7 +355,7 @@ func (i *iaasClient) DeleteVolume(ctx context.Context, volumeID string) error {
return fmt.Errorf("cannot delete the volume %q, it's still attached to a node", volumeID)
}

_, err = withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err = execute(ctx, func(ctx context.Context) (any, error) {
return nil, i.Client.DeleteVolume(ctx, i.projectID, i.region, volumeID).Execute()
})
return err
Expand All @@ -372,7 +372,7 @@ func (i *iaasClient) AttachVolume(ctx context.Context, serverID, volumeID string
return *volume.Id, nil
}

_, err = withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err = execute(ctx, func(ctx context.Context) (any, error) {
return i.Client.
AddVolumeToServer(ctx, i.projectID, i.region, serverID, volumeID).
AddVolumeToServerPayload(payload).
Expand All @@ -386,13 +386,13 @@ func (i *iaasClient) AttachVolume(ctx context.Context, serverID, volumeID string
}

func (i *iaasClient) GetVolume(ctx context.Context, volumeID string) (*iaas.Volume, error) {
return withResponseID(ctx, func(ctx context.Context) (*iaas.Volume, error) {
return execute(ctx, func(ctx context.Context) (*iaas.Volume, error) {
return i.Client.GetVolume(ctx, i.projectID, i.region, volumeID).Execute()
})
}

func (i *iaasClient) GetVolumesByName(ctx context.Context, volName string) ([]iaas.Volume, error) {
resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) {
resp, err := execute(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) {
return i.Client.ListVolumes(ctx, i.projectID, i.region).Execute()
})
if err != nil {
Expand All @@ -407,7 +407,7 @@ func (i *iaasClient) GetVolumesByName(ctx context.Context, volName string) ([]ia

func (i *iaasClient) ListVolumes(ctx context.Context, _ int, _ string) ([]iaas.Volume, string, error) {
// TODO: Add support for pagination when IaaS adds it
resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) {
resp, err := execute(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) {
return i.Client.ListVolumes(ctx, i.projectID, i.region).Execute()
})
if err != nil {
Expand All @@ -420,7 +420,7 @@ func (i *iaasClient) ListVolumes(ctx context.Context, _ int, _ string) ([]iaas.V
func (i *iaasClient) ExpandVolume(ctx context.Context, volumeID, volumeStatus string, payload iaas.ResizeVolumePayload) error {
switch volumeStatus {
case VolumeAttachedStatus, VolumeAvailableStatus:
_, err := withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err := execute(ctx, func(ctx context.Context) (any, error) {
return nil, i.Client.
ResizeVolume(ctx, i.projectID, i.region, volumeID).
ResizeVolumePayload(payload).
Expand Down Expand Up @@ -525,7 +525,7 @@ func (i *iaasClient) DetachVolume(ctx context.Context, serverID, volumeID string
}

if volume.ServerId != nil && *volume.ServerId == serverID {
_, err := withResponseID(ctx, func(ctx context.Context) (any, error) {
_, err := execute(ctx, func(ctx context.Context) (any, error) {
err := i.Client.RemoveVolumeFromServer(ctx, i.projectID, i.region, serverID, volumeID).Execute()
if err != nil {
return nil, fmt.Errorf("failed to detach volume %s from compute %s : %w", *volume.Name, serverID, err)
Expand Down
18 changes: 9 additions & 9 deletions pkg/stackit/client/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewLoadBalancingClient(region, projectID string, options []sdkconfig.Config
}

func (l *loadBalancingClient) CreateLoadBalancer(ctx context.Context, payload *loadbalancer.CreateLoadBalancerPayload) (*loadbalancer.LoadBalancer, error) {
return withResponseID(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return execute(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return l.Client.
CreateLoadBalancer(ctx, l.projectID, l.region).
CreateLoadBalancerPayload(*payload).
Expand All @@ -50,7 +50,7 @@ func (l *loadBalancingClient) CreateLoadBalancer(ctx context.Context, payload *l
}

func (l *loadBalancingClient) DeleteLoadBalancer(ctx context.Context, lbName string) error {
_, err := withResponseID(ctx, func(ctx context.Context) (map[string]any, error) {
_, err := execute(ctx, func(ctx context.Context) (map[string]any, error) {
return l.Client.
DeleteLoadBalancer(ctx, l.projectID, l.region, lbName).
Execute()
Expand All @@ -59,15 +59,15 @@ func (l *loadBalancingClient) DeleteLoadBalancer(ctx context.Context, lbName str
}

func (l *loadBalancingClient) GetLoadBalancer(ctx context.Context, lbName string) (*loadbalancer.LoadBalancer, error) {
return withResponseID(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return execute(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return l.Client.
GetLoadBalancer(ctx, l.projectID, l.region, lbName).
Execute()
})
}

func (l *loadBalancingClient) UpdateLoadBalancer(ctx context.Context, lbName string, updates *loadbalancer.UpdateLoadBalancerPayload) (*loadbalancer.LoadBalancer, error) {
return withResponseID(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return execute(ctx, func(ctx context.Context) (*loadbalancer.LoadBalancer, error) {
return l.Client.
UpdateLoadBalancer(ctx, l.projectID, l.region, lbName).
UpdateLoadBalancerPayload(*updates).
Expand All @@ -76,7 +76,7 @@ func (l *loadBalancingClient) UpdateLoadBalancer(ctx context.Context, lbName str
}

func (l *loadBalancingClient) UpdateTargetPool(ctx context.Context, name, targetPoolName string, payload loadbalancer.UpdateTargetPoolPayload) error {
_, err := withResponseID(ctx, func(ctx context.Context) (*loadbalancer.TargetPool, error) {
_, err := execute(ctx, func(ctx context.Context) (*loadbalancer.TargetPool, error) {
return l.Client.
UpdateTargetPool(ctx, l.projectID, l.region, name, targetPoolName).
UpdateTargetPoolPayload(payload).
Expand All @@ -86,7 +86,7 @@ func (l *loadBalancingClient) UpdateTargetPool(ctx context.Context, name, target
}

func (l *loadBalancingClient) CreateCredentials(ctx context.Context, payload loadbalancer.CreateCredentialsPayload) (*loadbalancer.CreateCredentialsResponse, error) {
return withResponseID(ctx, func(ctx context.Context) (*loadbalancer.CreateCredentialsResponse, error) {
return execute(ctx, func(ctx context.Context) (*loadbalancer.CreateCredentialsResponse, error) {
return l.Client.
CreateCredentials(ctx, l.projectID, l.region).
CreateCredentialsPayload(payload).
Expand All @@ -96,15 +96,15 @@ func (l *loadBalancingClient) CreateCredentials(ctx context.Context, payload loa
}

func (l *loadBalancingClient) ListCredentials(ctx context.Context) (*loadbalancer.ListCredentialsResponse, error) {
return withResponseID(ctx, func(ctx context.Context) (*loadbalancer.ListCredentialsResponse, error) {
return execute(ctx, func(ctx context.Context) (*loadbalancer.ListCredentialsResponse, error) {
return l.Client.
ListCredentials(ctx, l.projectID, l.region).
Execute()
})
}

func (l *loadBalancingClient) UpdateCredentials(ctx context.Context, credentialsRef string, payload loadbalancer.UpdateCredentialsPayload) error {
_, err := withResponseID(ctx, func(ctx context.Context) (*loadbalancer.UpdateCredentialsResponse, error) {
_, err := execute(ctx, func(ctx context.Context) (*loadbalancer.UpdateCredentialsResponse, error) {
return l.Client.
UpdateCredentials(ctx, l.projectID, l.region, credentialsRef).
UpdateCredentialsPayload(payload).
Expand All @@ -114,7 +114,7 @@ func (l *loadBalancingClient) UpdateCredentials(ctx context.Context, credentials
}

func (l *loadBalancingClient) DeleteCredentials(ctx context.Context, credentialsRef string) error {
_, err := withResponseID(ctx, func(ctx context.Context) (map[string]any, error) {
_, err := execute(ctx, func(ctx context.Context) (map[string]any, error) {
return l.Client.
DeleteCredentials(ctx, l.projectID, l.region, credentialsRef).
Execute()
Expand Down
14 changes: 9 additions & 5 deletions pkg/stackit/stackiterrors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ func IgnoreNotFound(err error) error {
return err
}

// WrapErrorWithResponseID wraps the error with the X-Request-Id but only if the error is not nil
func WrapErrorWithResponseID(err error, reqID string) error {
// WrapError wraps the error with an identifier but only if the error is not nil.
func WrapError(err error, name, id string) error {
if err == nil {
return nil
}
// if the request id is empty we don't wrap the error
if reqID == "" {
if id == "" {
return err
}
return fmt.Errorf("[%s:%s]: %w", wait.XRequestIDHeader, reqID, err)
return fmt.Errorf("[%s:%s]: %w", name, id, err)
}

// WrapErrorWithResponseID wraps the error with the X-Request-Id.
func WrapErrorWithResponseID(err error, reqID string) error {
return WrapError(err, wait.XRequestIDHeader, reqID)
}

func IsInvalidError(err error) bool {
Expand Down
17 changes: 17 additions & 0 deletions pkg/stackit/stackiterrors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ var _ = Describe("Errors", func() {
})
})

Describe("WrapError", func() {
It("wraps the error with the provided identifier", func() {
err := errors.New("test error")
expected := fmt.Errorf("[X-Trace-Id:12345]: %w", err)
Expect(WrapError(err, "X-Trace-Id", "12345")).To(Equal(expected))
})

It("returns the original error when the identifier is empty", func() {
err := errors.New("test error")
Expect(WrapError(err, "trace-id", "")).To(Equal(err))
})

It("returns nil when the error is nil", func() {
Expect(WrapError(nil, "trace-id", "12345")).To(Succeed())
})
})

Describe("IsInvalidError", func() {
Context("when error is a BadRequest error", func() {
It("should return true", func() {
Expand Down