Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Database as a Service (DBaaS) support with instances, instance types, and engines #37

Merged
merged 4 commits into from
Jan 29, 2025
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ go get github.com/MagaluCloud/mgc-sdk-go
- Audit
- Events
- Events Types
- Database as a Service (DBaaS)
- Instances
- Instance Types
- Snapshots
- Replicas
- Engines
- Container Registry
- Repositories
- Registries
- Images
- Credentials

## Authentication

Expand Down
129 changes: 129 additions & 0 deletions cmd/examples/dbaas/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/MagaluCloud/mgc-sdk-go/client"
"github.com/MagaluCloud/mgc-sdk-go/dbaas"
"github.com/MagaluCloud/mgc-sdk-go/helpers"
)

func main() {
ExampleListEngines()
ExampleListInstanceTypes()
ExampleListInstances()
ExampleCreateInstance()
}

func ExampleListEngines() {
apiToken := os.Getenv("MGC_API_TOKEN")
if apiToken == "" {
log.Fatal("MGC_API_TOKEN environment variable is not set")
}
c := client.NewMgcClient(apiToken)
dbaasClient := dbaas.New(c)

engines, err := dbaasClient.Engines().List(context.Background(), dbaas.ListEngineOptions{
Limit: helpers.IntPtr(10),
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("Found %d database engines:\n", len(engines))
for _, engine := range engines {
fmt.Printf("Engine: %s (ID: %s)\n", engine.Name, engine.ID)
fmt.Printf(" Version: %s\n", engine.Version)
fmt.Printf(" Status: %s\n", engine.Status)
}
}

func ExampleListInstanceTypes() {
apiToken := os.Getenv("MGC_API_TOKEN")
if apiToken == "" {
log.Fatal("MGC_API_TOKEN environment variable is not set")
}
c := client.NewMgcClient(apiToken)
dbaasClient := dbaas.New(c)

instanceTypes, err := dbaasClient.InstanceTypes().List(context.Background(), dbaas.ListInstanceTypeOptions{
Limit: helpers.IntPtr(10),
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("Found %d instance types:\n", len(instanceTypes))
for _, instanceType := range instanceTypes {
fmt.Printf("Instance Type: %s (ID: %s)\n", instanceType.Name, instanceType.ID)
fmt.Printf(" Label: %s\n", instanceType.Label)
fmt.Printf(" VCPU: %s\n", instanceType.VCPU)
fmt.Printf(" RAM: %s\n", instanceType.RAM)
fmt.Printf(" Family: %s (%s)\n", instanceType.FamilyDescription, instanceType.FamilySlug)
}
}

func ExampleListInstances() {
apiToken := os.Getenv("MGC_API_TOKEN")
if apiToken == "" {
log.Fatal("MGC_API_TOKEN environment variable is not set")
}
c := client.NewMgcClient(apiToken)
dbaasClient := dbaas.New(c)

instances, err := dbaasClient.Instances().List(context.Background(), dbaas.ListInstanceOptions{
Limit: helpers.IntPtr(10),
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("Found %d database instances:\n", len(instances))
for _, instance := range instances {
fmt.Printf("Instance: %s (ID: %s)\n", instance.Name, instance.ID)
fmt.Printf(" Engine ID: %s\n", instance.EngineID)
fmt.Printf(" Status: %s\n", instance.Status)
fmt.Printf(" Volume Size: %d GB\n", instance.Volume.Size)
fmt.Printf(" Volume Type: %s\n", instance.Volume.Type)
if len(instance.Addresses) > 0 {
fmt.Println(" Addresses:")
for _, addr := range instance.Addresses {
if addr.Address != nil {
fmt.Printf(" %s (%s): %s\n", addr.Access, *addr.Type, *addr.Address)
}
}
}
}
}

func ExampleCreateInstance() {
apiToken := os.Getenv("MGC_API_TOKEN")
if apiToken == "" {
log.Fatal("MGC_API_TOKEN environment variable is not set")
}
c := client.NewMgcClient(apiToken)
dbaasClient := dbaas.New(c)

// Create a new database instance
instance, err := dbaasClient.Instances().Create(context.Background(), dbaas.InstanceCreateRequest{
Name: "example-db-instance",
EngineID: "your-engine-id", // Replace with actual engine ID
InstanceTypeID: "your-instance-type-id", // Replace with actual instance type ID
User: "dbadmin",
Password: "YourStrongPassword123!",
Volume: dbaas.InstanceVolumeRequest{
Size: 20, // Size in GB
Type: dbaas.VolumeTypeCloudNVME,
},
BackupRetentionDays: 7,
BackupStartAt: "02:00", // Start backup at 2 AM
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("Successfully created database instance with ID: %s\n", instance.ID)
}
55 changes: 55 additions & 0 deletions dbaas/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dbaas

import (
"context"
"net/http"

"github.com/MagaluCloud/mgc-sdk-go/client"
mgc_http "github.com/MagaluCloud/mgc-sdk-go/internal/http"
)

const (
DefaultBasePath = "/database"
)

type DBaaSClient struct {
*client.CoreClient
}

type ClientOption func(*DBaaSClient)

func New(core *client.CoreClient, opts ...ClientOption) *DBaaSClient {
if core == nil {
return nil
}

client := &DBaaSClient{
CoreClient: core,
}

for _, opt := range opts {
opt(client)
}

return client
}

func (c *DBaaSClient) newRequest(ctx context.Context, method, path string, body any) (*http.Request, error) {
return mgc_http.NewRequest(c.GetConfig(), ctx, method, DefaultBasePath+path, &body)
}

func (c *DBaaSClient) Engines() EngineService {
return &engineService{client: c}
}

func (c *DBaaSClient) InstanceTypes() InstanceTypeService {
return &instanceTypeService{client: c}
}

func (c *DBaaSClient) Instances() InstanceService {
return &instanceService{client: c}
}

func (c *DBaaSClient) Replicas() ReplicaService {
return &replicaService{client: c}
}
72 changes: 72 additions & 0 deletions dbaas/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package dbaas

import (
"net/http"
"testing"

"github.com/MagaluCloud/mgc-sdk-go/client"
)

func TestNew(t *testing.T) {
tests := []struct {
name string
core *client.CoreClient
wantNil bool
}{
{
name: "valid core client",
core: client.NewMgcClient("test-token"),
wantNil: false,
},
{
name: "nil core client",
core: nil,
wantNil: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := New(tt.core)
if (client == nil) != tt.wantNil {
t.Errorf("New() returned nil = %v, want nil = %v", client == nil, tt.wantNil)
}
})
}
}

func TestDBaaSClient_Services(t *testing.T) {
core := client.NewMgcClient("test-token",
client.WithHTTPClient(&http.Client{}),
client.WithBaseURL("https://api.test.com"))

dbaas := New(core)

t.Run("Engines service", func(t *testing.T) {
service := dbaas.Engines()
if service == nil {
t.Error("Engines() returned nil")
}
})

t.Run("InstanceTypes service", func(t *testing.T) {
service := dbaas.InstanceTypes()
if service == nil {
t.Error("InstanceTypes() returned nil")
}
})

t.Run("Instances service", func(t *testing.T) {
service := dbaas.Instances()
if service == nil {
t.Error("Instances() returned nil")
}
})

t.Run("Replicas service", func(t *testing.T) {
service := dbaas.Replicas()
if service == nil {
t.Error("Replicas() returned nil")
}
})
}
112 changes: 112 additions & 0 deletions dbaas/engines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package dbaas

import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"

mgc_http "github.com/MagaluCloud/mgc-sdk-go/internal/http"
)

type (
ListEnginesResponse struct {
Meta MetaResponse `json:"meta"`
Results []EngineDetail `json:"results"`
}

MetaResponse struct {
Page PageResponse `json:"page"`
Filters []FieldValueFilter `json:"filters"`
}

PageResponse struct {
Offset int `json:"offset"`
Limit int `json:"limit"`
Count int `json:"count"`
Total int `json:"total"`
MaxLimit int `json:"max_limit"`
}

FieldValueFilter struct {
Field string `json:"field"`
Value string `json:"value"`
}

EngineDetail struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Status EngineStatus `json:"status"`
}
)

// EngineStatus represents the status of a database engine
type EngineStatus string

const (
EngineStatusActive EngineStatus = "ACTIVE"
EngineStatusDeprecated EngineStatus = "DEPRECATED"
)

type (
EngineService interface {
// List returns all available database engines
List(ctx context.Context, opts ListEngineOptions) ([]EngineDetail, error)

// Get retrieves detailed information about a specific engine
Get(ctx context.Context, id string) (*EngineDetail, error)
}

engineService struct {
client *DBaaSClient
}

ListEngineOptions struct {
Offset *int
Limit *int
Status *EngineStatus
}
)

func (s *engineService) List(ctx context.Context, opts ListEngineOptions) ([]EngineDetail, error) {
query := make(url.Values)

if opts.Offset != nil {
query.Set("_offset", strconv.Itoa(*opts.Offset))
}
if opts.Limit != nil {
query.Set("_limit", strconv.Itoa(*opts.Limit))
}
if opts.Status != nil {
query.Set("status", string(*opts.Status))
}

result, err := mgc_http.ExecuteSimpleRequestWithRespBody[ListEnginesResponse](
ctx,
s.client.newRequest,
s.client.GetConfig(),
http.MethodGet,
"/v1/engines",
nil,
query,
)
if err != nil {
return nil, err
}

return result.Results, nil
}

func (s *engineService) Get(ctx context.Context, id string) (*EngineDetail, error) {
return mgc_http.ExecuteSimpleRequestWithRespBody[EngineDetail](
ctx,
s.client.newRequest,
s.client.GetConfig(),
http.MethodGet,
fmt.Sprintf("/v1/engines/%s", id),
nil,
nil,
)
}
Loading
Loading