Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## Release (2025-XX-YY)
- `logs`:
- [v0.1.0](services/logs/CHANGELOG.md#v010)
- [v0.2.0](services/logs/CHANGELOG.md#v020)
- **Feature:** Add new wait handlers for instance creation (`CreateLogsInstanceWaitHandler`), and instance deletion (`DeleteLogsInstanceWaitHandler`)
- [v0.1.0](services/logs/CHANGELOG.md#v010)
- **New:** API for logs service
## Release (2025-12-05)
- `alb`:
Expand Down
13 changes: 13 additions & 0 deletions examples/logs/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/stackitcloud/stackit-sdk-go/examples/logs

go 1.21

require (
github.com/stackitcloud/stackit-sdk-go/core v0.20.1
github.com/stackitcloud/stackit-sdk-go/services/logs v0.1.0
)

require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
)
10 changes: 10 additions & 0 deletions examples/logs/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/stackitcloud/stackit-sdk-go/core v0.20.1 h1:odiuhhRXmxvEvnVTeZSN9u98edvw2Cd3DcnkepncP3M=
github.com/stackitcloud/stackit-sdk-go/core v0.20.1/go.mod h1:fqto7M82ynGhEnpZU6VkQKYWYoFG5goC076JWXTUPRQ=
github.com/stackitcloud/stackit-sdk-go/services/logs v0.1.0 h1:Fck91pz2Oxk8dUd2lOdnsIMWSCzBzAHJp7ivqAJ59is=
github.com/stackitcloud/stackit-sdk-go/services/logs v0.1.0/go.mod h1:VM+++rhzI2/lvhyGKg0FCiEfnrADWykcdHLbECrl6T0=
102 changes: 102 additions & 0 deletions examples/logs/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"context"
"log"

"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/logs"
)

func main() {
ctx := context.Background()

projectId := "PROJECT_ID" // the uuid of your STACKIT project
regionId := "eu01"

client, err := logs.NewAPIClient(
config.WithRegion(regionId),
)
if err != nil {
log.Fatalf("[Logs API] Creating API client: %v\n", err)
}

// Create a Logs Instance
var createdInstance string
createInstancePayload := logs.CreateLogsInstancePayload{
DisplayName: utils.Ptr("my-logs-instance"),
RetentionDays: utils.Ptr(int64(1)),
}
createResp, err := client.CreateLogsInstance(ctx, projectId, regionId).
CreateLogsInstancePayload(createInstancePayload).
Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `CreateLogsInstance`: %v\n", err)
}
createdInstance = *createResp.Id
log.Printf("[Logs API] Created Logs Instance with ID \"%s\".\n", createdInstance)

// List Logs Instances
listResp, err := client.ListLogsInstances(ctx, projectId, regionId).Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `ListLogsInstances`: %v\n", err)
}
log.Printf("[Logs API] Retrieved %d Logs Instances.\n", len(*listResp.Instances))

// Get the created Logs Instance
getResp, err := client.GetLogsInstance(ctx, projectId, regionId, createdInstance).Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `GetLogsInstance`: %v\n", err)
}
log.Printf("[Logs API] Retrieved Logs Instance with ID \"%s\" and Display Name \"%s\".\n", *getResp.Id, *getResp.DisplayName)

// Update the created Logs Instance
updatePayload := logs.UpdateLogsInstancePayload{
DisplayName: utils.Ptr("my-updated-logs-instance"),
RetentionDays: utils.Ptr(int64(7)),
}
updateResp, err := client.UpdateLogsInstance(ctx, projectId, regionId, createdInstance).
UpdateLogsInstancePayload(updatePayload).
Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `UpdateLogsInstance`: %v\n", err)
}
log.Printf("[Logs API] Updated Logs Instance with ID \"%s\" to Display Name \"%s\".\n", *updateResp.Id, *updateResp.DisplayName)

// Create an Access Token
createTokenPayload := logs.CreateAccessTokenPayload{
DisplayName: utils.Ptr("my-access-token"),
Permissions: &[]string{"read"},
}
createTokenResp, err := client.CreateAccessToken(ctx, projectId, regionId, createdInstance).
CreateAccessTokenPayload(createTokenPayload).
Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `CreateAccessToken`: %v\n", err)
}
log.Printf("[Logs API] Created Access Token with ID \"%s\".\n", *createTokenResp.Id)

// Add Access Token to Logs Instance
err = client.UpdateAccessToken(ctx, projectId, regionId, createdInstance, *createTokenResp.Id).
// needs at least an empty payload
UpdateAccessTokenPayload(logs.UpdateAccessTokenPayload{}).
Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `UpdateAccessToken`: %v\n", err)
}

// Delete all Access Tokens from Logs Instance
tokenList, err := client.DeleteAllAccessTokens(ctx, projectId, regionId, createdInstance).Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `DeleteAllAccessTokens`: %v\n", err)
}
log.Printf("[Logs API] Deleted %d Access Tokens from Logs Instance with ID \"%s\".\n", len(*tokenList.Tokens), createdInstance)

// Delete the created Logs Instance
err = client.DeleteLogsInstance(ctx, projectId, regionId, createdInstance).Execute()
if err != nil {
log.Fatalf("[Logs API] Error when calling `DeleteLogsInstance`: %v\n", err)
}
log.Printf("[Logs API] Deleted Logs Instance with ID \"%s\".\n", createdInstance)
}
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use (
./examples/kms
./examples/loadbalancer
./examples/logme
./examples/logs
./examples/mariadb
./examples/middleware
./examples/mongodbflex
Expand Down
3 changes: 3 additions & 0 deletions services/logs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
## v0.2.0
- **Feature:** Add new wait handlers for instance creation (`CreateLogsInstanceWaitHandler`), and instance deletion (`DeleteLogsInstanceWaitHandler`)

## v0.1.0
- **New:** API for logs service
1 change: 1 addition & 0 deletions services/logs/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/stackitcloud/stackit-sdk-go/services/logs
go 1.21

require (
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/stackitcloud/stackit-sdk-go/core v0.20.0
)
Expand Down
57 changes: 57 additions & 0 deletions services/logs/wait/wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package wait

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

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

type APIClientInterface interface {
GetLogsInstanceExecute(ctx context.Context, projectId string, regionId string, instanceId string) (*logs.LogsInstance, error)
}

func CreateLogsInstanceWaitHandler(ctx context.Context, client APIClientInterface, projectID, region, instanceID string) *wait.AsyncActionHandler[logs.LogsInstance] {
handler := wait.New(func() (waitFinished bool, response *logs.LogsInstance, err error) {
instance, err := client.GetLogsInstanceExecute(ctx, projectID, region, instanceID)
if err != nil {
return false, nil, err
}
if instance.Id == nil || instance.Status == nil {
return false, nil, fmt.Errorf("get instance, project: %q, region: %q, instanceID: %q: missing id or status", projectID, region, instanceID)
}
if *instance.Id == instanceID && *instance.Status == logs.LOGSINSTANCESTATUS_ACTIVE {
return true, instance, nil
}
if *instance.Status == logs.LOGSINSTANCESTATUS_DELETING {
return true, nil, fmt.Errorf("creating log instance failed, instance is being deleted")
}
return false, nil, nil
})
handler.SetTimeout(10 * time.Minute)
return handler
}

func DeleteLogsInstanceWaitHandler(ctx context.Context, client APIClientInterface, projectID, region, instanceID string) *wait.AsyncActionHandler[logs.LogsInstance] {
handler := wait.New(func() (waitFinished bool, response *logs.LogsInstance, err error) {
_, err = client.GetLogsInstanceExecute(ctx, projectID, region, instanceID)
// the instances is still gettable, e.g. not deleted, when the errors is null
if err == nil {
return false, nil, nil
}
var oapiError *oapierror.GenericOpenAPIError
if errors.As(err, &oapiError) {
if statusCode := oapiError.StatusCode; statusCode == http.StatusNotFound {
return true, nil, nil
}
}
return false, nil, err
})
handler.SetTimeout(10 * time.Minute)
return handler
}
169 changes: 169 additions & 0 deletions services/logs/wait/wait_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package wait

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

"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/logs"
)

type apiClientMock struct {
getFails bool
returnInstance bool
statusCode int
getLogsResponse *logs.LogsInstance
}

func (a *apiClientMock) GetLogsInstanceExecute(_ context.Context, _, _, _ string) (*logs.LogsInstance, error) {
if a.getFails {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: a.statusCode,
}
}
if !a.returnInstance {
return nil, nil
}
return a.getLogsResponse, nil
}

var projectId = uuid.NewString()
var instanceId = uuid.NewString()

const region = "eu01"

func TestCreateLogsInstanceWaitHandler(t *testing.T) {
tests := []struct {
description string
getFails bool
wantErr bool
wantResp bool
returnInstance bool
getLogsResponse *logs.LogsInstance
}{
{
description: "create succeeded",
getFails: false,
wantErr: false,
wantResp: true,
returnInstance: true,
getLogsResponse: &logs.LogsInstance{
Id: utils.Ptr(instanceId),
Status: utils.Ptr(logs.LOGSINSTANCESTATUS_ACTIVE),
},
},
{
description: "create failed with error",
getFails: true,
wantErr: true,
wantResp: false,
returnInstance: true,
getLogsResponse: &logs.LogsInstance{
Id: utils.Ptr(instanceId),
Status: utils.Ptr(logs.LOGSINSTANCESTATUS_ACTIVE),
},
},
{
description: "create without id",
getFails: false,
wantErr: true,
wantResp: false,
returnInstance: true,
getLogsResponse: &logs.LogsInstance{
Status: utils.Ptr(logs.LOGSINSTANCESTATUS_ACTIVE),
},
},
{
description: "create without status",
getFails: false,
wantErr: true,
wantResp: false,
returnInstance: true,
getLogsResponse: &logs.LogsInstance{
Id: utils.Ptr(instanceId),
},
},
{
description: "instance deleting",
getFails: false,
wantErr: true,
wantResp: false,
returnInstance: true,
getLogsResponse: &logs.LogsInstance{
Id: utils.Ptr(instanceId),
Status: utils.Ptr(logs.LOGSINSTANCESTATUS_DELETING),
},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &apiClientMock{
getFails: tt.getFails,
getLogsResponse: tt.getLogsResponse,
returnInstance: tt.returnInstance,
}
var instanceWanted *logs.LogsInstance
if tt.wantResp {
instanceWanted = tt.getLogsResponse
}

handler := CreateLogsInstanceWaitHandler(context.Background(), client, projectId, region, instanceId)

response, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())

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

func TestDeleteLogsInstanceWaitHandler(t *testing.T) {
tests := []struct {
description string
getFails bool
wantErr bool
statusCode int
}{
{
description: "delete succeeded",
getFails: true,
statusCode: http.StatusNotFound,
},
{
description: "delete failed with error",
getFails: true,
wantErr: true,
statusCode: http.StatusInternalServerError,
},
{
description: "delete still in progress",
getFails: false,
wantErr: true,
statusCode: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &apiClientMock{
getFails: tt.getFails,
returnInstance: false,
statusCode: tt.statusCode,
getLogsResponse: nil,
}
handler := DeleteLogsInstanceWaitHandler(context.Background(), client, projectId, region, instanceId)
_, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())
if (err != nil) != tt.wantErr {
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}