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
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
>
> This SDK was created by the community due to the lack of an official NetApp StorageGRID SDK for Go. It is designed to fulfill the needs of its maintainers and contributors. If you find something missing or spot a bug, please open an [issue](https://github.com/bedag/storagegrid-sdk-go/issues) or submit a [pull request](https://github.com/bedag/storagegrid-sdk-go/pulls)! Contributions are highly encouraged.

> [!NOTE]
> This SDK was originally developed by an employee and it's history can be seen in the [original repository](https://github.com/bedag/storagegrid-sdk-go). The original repository is no longer actively maintained, and this copy serves as the new home for ongoing development and contributions from the community.

## Table of Contents

- [Overview](#overview)
Expand Down Expand Up @@ -43,9 +40,10 @@ This SDK reflects this architecture with corresponding client types. For more de
- **Regions**: List available regions for grid and tenant contexts
- **HA Groups**: Manage High Availability groups
- **Gateway Configs**: Configure load balancer endpoints
- **S3 Object Lock**: Read and update grid-wide compliance (S3 Object Lock) settings

### Tenant Management
- **Buckets**: Create, list, delete, drain buckets; monitor bucket usage and compliance settings
- **Buckets**: Create, list, delete, drain buckets; manage per-bucket sub-resources (region, usage, S3 Object Lock, notifications, policy, CORS, legacy compliance)
- **Users**: Manage tenant users with password management
- **Groups**: Manage tenant groups with policies and permissions
- **S3 Access Keys**: Generate and manage S3 access keys for users
Expand Down Expand Up @@ -291,8 +289,8 @@ fmt.Printf("Secret Key: %s\n", *keys.SecretAccessKey)

Comprehensive examples are available in the [`examples/`](examples/) directory:

- **[Grid Management](examples/grid/)**: Health monitoring, tenant management
- **[Tenant Operations](examples/tenant/)**: Bucket operations, user management
- **[Grid Management](examples/grid/)**: Health monitoring, tenant management, end-to-end S3 Object Lock
- **[Tenant Operations](examples/tenant/)**: Bucket operations (incl. per-bucket sub-resources)
- **[Testing](examples/testing/)**: Unit tests with mocks, integration tests

### Quick Examples
Expand Down Expand Up @@ -434,6 +432,7 @@ The `testing` package provides mocks for all service interfaces:
- `MockHAGroupService` - HA group management
- `MockGatewayConfigService` - Gateway configuration
- `MockRegionService` - Region management
- `MockS3ObjectLockService` - Grid-wide S3 Object Lock (compliance-global) settings

## API Coverage

Expand All @@ -442,25 +441,27 @@ This SDK provides access to StorageGRID's dual API architecture:
### Grid Management APIs (GridClient)
Used for system-wide administration with grid administrator credentials:

| Service | Endpoint | Operations | Description |
|---------|----------|------------|-------------|
| **Tenants** | `/grid/accounts` | Create, Read, Update, Delete, List | Manage tenant accounts |
| **Health** | `/grid/health` | Read | Monitor grid health, alarms, alerts, node status |
| **Regions** | `/grid/regions` | List | Manage grid-wide regions |
| **HA Groups** | `/private/ha-groups` | Create, Read, Update, Delete, List | Configure High Availability groups |
| **Gateways** | `/private/gateway-configs` | Create, Read, Update, Delete, List | Manage load balancer endpoints |
| Service | Endpoint | Operations | Description |
| ------------------ | -------------------------- | ---------------------------------- | ----------------------------------------------------- |
| **Tenants** | `/grid/accounts` | Create, Read, Update, Delete, List | Manage tenant accounts |
| **Health** | `/grid/health` | Read | Monitor grid health, alarms, alerts, node status |
| **Regions** | `/grid/regions` | List | Manage grid-wide regions |
| **HA Groups** | `/private/ha-groups` | Create, Read, Update, Delete, List | Configure High Availability groups |
| **Gateways** | `/private/gateway-configs` | Create, Read, Update, Delete, List | Manage load balancer endpoints |
| **S3 Object Lock** | `/grid/compliance-global` | Read, Update | Manage grid-wide S3 Object Lock (compliance) settings |

### Tenant Management APIs (TenantClient)
Used for tenant-specific operations with tenant user credentials:

| Service | Endpoint | Operations | Description |
|---------|----------|------------|-------------|
| **Buckets** | `/org/containers` | Create, Read, Delete, List, Drain | Manage S3 buckets within tenant |
| **Users** | `/org/users` | Create, Read, Update, Delete, List | Manage tenant users |
| **Groups** | `/org/groups` | Create, Read, Update, Delete, List | Manage tenant groups and permissions |
| **S3 Keys** | `/org/users/*/s3-access-keys` | Create, Read, Delete, List | Generate and manage S3 access credentials |
| **Regions** | `/org/regions` | List | List tenant-accessible regions |
| **Usage** | `/org/usage` | Read | Monitor tenant usage statistics |
| Service | Endpoint | Operations | Description |
| ------------------------ | --------------------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Buckets** | `/org/containers` | Create, Read, Delete, List, Drain | Manage S3 buckets within tenant |
| **Bucket sub-resources** | `/org/containers/{name}/{region,usage,object-lock,notification,policy,cors,compliance}` | Read, Update | Per-bucket configuration: region, usage, S3 Object Lock, notifications, bucket policy, CORS, legacy compliance |
| **Users** | `/org/users` | Create, Read, Update, Delete, List | Manage tenant users |
| **Groups** | `/org/groups` | Create, Read, Update, Delete, List | Manage tenant groups and permissions |
| **S3 Keys** | `/org/users/*/s3-access-keys` | Create, Read, Delete, List | Generate and manage S3 access credentials |
| **Regions** | `/org/regions` | List | List tenant-accessible regions |
| **Usage** | `/org/usage` | Read | Monitor tenant usage statistics |

> 📚 **Official Documentation**: For comprehensive API documentation, refer to the [NetApp StorageGRID REST API Reference](https://docs.netapp.com/us-en/storagegrid-115/s3/storagegrid-s3-rest-api-operations.html).

Expand Down
28 changes: 17 additions & 11 deletions client/grid.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ type GridClient struct {
client *Client

// Services
tenant services.TenantServiceInterface
health services.HealthServiceInterface
region services.RegionServiceInterface
haGroup services.HAGroupServiceInterface
gateway services.GatewayConfigServiceInterface
tenant services.TenantServiceInterface
health services.HealthServiceInterface
region services.RegionServiceInterface
haGroup services.HAGroupServiceInterface
gateway services.GatewayConfigServiceInterface
s3ObjectLock services.S3ObjectLockServiceInterface
}

func NewGridClient(options ...ClientOption) (*GridClient, error) {
Expand All @@ -30,12 +31,13 @@ func NewGridClient(options ...ClientOption) (*GridClient, error) {
c.baseURL = c.baseURL.ResolveReference(&url.URL{Path: gridAPI})

return &GridClient{
client: c,
tenant: services.NewTenantService(c),
health: services.NewHealthService(c),
region: services.NewRegionGridService(c),
haGroup: services.NewHAGroupService(c),
gateway: services.NewGatewayConfigService(c),
client: c,
tenant: services.NewTenantService(c),
health: services.NewHealthService(c),
region: services.NewRegionGridService(c),
haGroup: services.NewHAGroupService(c),
gateway: services.NewGatewayConfigService(c),
s3ObjectLock: services.NewS3ObjectLockService(c),
}, nil
}

Expand All @@ -60,3 +62,7 @@ func (gc *GridClient) HAGroup() services.HAGroupServiceInterface {
func (gc *GridClient) Gateway() services.GatewayConfigServiceInterface {
return gc.gateway
}

func (gc *GridClient) S3ObjectLock() services.S3ObjectLockServiceInterface {
return gc.s3ObjectLock
}
77 changes: 77 additions & 0 deletions examples/grid/s3-object-lock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# S3 Object Lock Example (end-to-end)

This example walks through the full S3 Object Lock flow against a StorageGRID
deployment:

1. Reads the grid-wide compliance-global settings via the **GridClient**
(`/grid/compliance-global`).
2. Optionally enables S3 Object Lock grid-wide (set `STORAGEGRID_APPLY=true`).
3. Updates the target tenant's policy (`allowComplianceMode`,
`maxRetentionYears`) so the tenant can use S3 Object Lock and bucket
retention is capped grid-side.
4. Creates a bucket via the **TenantClient** with S3 Object Lock enabled and a
default compliance-mode retention period.
5. Reads the per-bucket Object Lock settings back via the dedicated
`/org/containers/{bucketName}/object-lock` sub-resource.

> ⚠️ **Enabling S3 Object Lock grid-wide is irreversible.** See the
> [official documentation](https://docs.netapp.com/us-en/storagegrid/ilm/managing-objects-with-s3-object-lock.html#what-is-s3-object-lock).
and tenant policy
update)
- Tenant administrator credentials and an account ID (for the bucket stepsdentials (for the grid-wide settings)
- Tenant administrator credentials and an account ID (for the bucket steps)
- The tenant must have S3 Object Lock allowed via its tenant policy
(`allowComplianceMode`)

## Environment Variables

```bash
# Required for the grid-side steps
export STORAGEGRID_ENDPOINT="https://your-storagegrid.example.com"
export STORAGEGRID_USERNAME="grid-admin"
export STORAGEGRID_PASSWORD="grid-password"

# Required for the tenant/bucket steps
export STORAGEGRID_TENANT_USERNAME="tenant-admin"
export STORAGEGRID_TENANT_PASSWORD="tenant-password"
export STORAGEGRID_ACCOUNT_ID="12345678901234567890"

# Optional
export STORAGEGRID_SKIP_SSL="true" # development only
export STORAGEGRID_APPLY="true" # actually enable S3 Object Lock grid-wide
```

If `STORAGEGRID_APPLY` is not set, the example only reads grid-wide settings
and does not create any buckets.

## Running

```bash
cd examples/grid/s3-object-lock
go mod init s3-object-lock-example
go mod tidy
go run main.go
```

## What you should see

```
🔍 Grid-wide S3 Object Lock settings (/grid/compliance-global)

📊 Current:
complianceEnabled: true
legacyComplianceEnabled: false
createLegacyComplianceBuckets: false

🏗️ Creating bucket "object-lock-demo-20260428-101530" with S3 Object Lock enabled...
🔐 Updating tenant policy to allow S3 Object Lock...
✅ allowComplianceMode=true, maxRetentionYears=10 on tenant 12345678901234567890

✅ Created object-lock-demo-20260428-101530 in us-east-1

🔍 Reading bucket Object Lock settings (/org/containers/<name>/object-lock)...
enabled: true
defaultRetentionSetting:
mode: compliance
days: 30
```
183 changes: 183 additions & 0 deletions examples/grid/s3-object-lock/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package main

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

"github.com/bedag/storagegrid-sdk-go/client"
"github.com/bedag/storagegrid-sdk-go/models"
)

// End-to-end S3 Object Lock example.
//
// 1. Reads the grid-wide compliance-global settings via the GridClient.
// 2. Optionally enables S3 Object Lock grid-wide (irreversible).
// 3. Updates the tenant policy to allow S3 Object Lock for the target tenant
// and caps the maximum retention a bucket may specify.
// 4. Creates a bucket via the TenantClient with S3 Object Lock enabled and a
// default compliance-mode retention period.
// 5. Reads the per-bucket Object Lock settings back using the new
// /org/containers/{bucketName}/object-lock endpoint.
//
// See: https://docs.netapp.com/us-en/storagegrid/ilm/managing-objects-with-s3-object-lock.html
func main() {
endpoint := os.Getenv("STORAGEGRID_ENDPOINT")
gridUser := os.Getenv("STORAGEGRID_USERNAME")
gridPass := os.Getenv("STORAGEGRID_PASSWORD")
tenantUser := os.Getenv("STORAGEGRID_TENANT_USERNAME")
tenantPass := os.Getenv("STORAGEGRID_TENANT_PASSWORD")
accountID := os.Getenv("STORAGEGRID_ACCOUNT_ID")
skipSSL := os.Getenv("STORAGEGRID_SKIP_SSL") == "true"
apply := os.Getenv("STORAGEGRID_APPLY") == "true"

if endpoint == "" || gridUser == "" || gridPass == "" {
log.Fatal("Required env vars: STORAGEGRID_ENDPOINT, STORAGEGRID_USERNAME, STORAGEGRID_PASSWORD")
}

ctx := context.Background()

gridClient, err := client.NewGridClient(buildOpts(endpoint, gridUser, gridPass, nil, skipSSL)...)
if err != nil {
log.Fatalf("Failed to create grid client: %v", err)
}

// 1. Inspect grid-wide settings.
fmt.Println("🔍 Grid-wide S3 Object Lock settings (/grid/compliance-global)")
current, err := gridClient.S3ObjectLock().Get(ctx)
if err != nil {
log.Fatalf("Failed to get S3 Object Lock settings: %v", err)
}
printGridSettings("Current", current)

// 2. Optionally enable grid-wide.
if apply {
enable := true
fmt.Println("\n✏️ Enabling complianceEnabled grid-wide (irreversible)...")
updated, err := gridClient.S3ObjectLock().Update(ctx, &models.S3ObjectLock{ComplianceEnabled: &enable})
if err != nil {
log.Fatalf("Failed to update S3 Object Lock settings: %v", err)
}
printGridSettings("Updated", updated)
current = updated
} else {
fmt.Println("\nℹ️ Set STORAGEGRID_APPLY=true to enable S3 Object Lock grid-wide.")
}

if current.ComplianceEnabled == nil || !*current.ComplianceEnabled {
fmt.Println("\n⚠️ Grid-wide S3 Object Lock is not enabled — skipping bucket demo.")
return
}

if tenantUser == "" || tenantPass == "" || accountID == "" {
fmt.Println("\nℹ️ Set STORAGEGRID_TENANT_USERNAME, STORAGEGRID_TENANT_PASSWORD, and STORAGEGRID_ACCOUNT_ID to run the bucket demo.")
return
}

// 3. Update the tenant policy: allow S3 Object Lock and cap max retention.
// Per-tenant settings live on TenantPolicy and gate what tenants can do
// with S3 Object Lock once it is enabled grid-wide.
fmt.Println("\n🔐 Updating tenant policy to allow S3 Object Lock...")
tenant, err := gridClient.Tenant().GetById(ctx, accountID)
if err != nil {
log.Fatalf("Failed to get tenant %s: %v", accountID, err)
}
if tenant.Policy == nil {
tenant.Policy = &models.TenantPolicy{}
}
allow := true
maxYears := 10
tenant.Policy.AllowComplianceMode = &allow
tenant.Policy.MaxRetentionYears = &maxYears
tenant.Policy.MaxRetentionDays = nil // no per-day cap (years cap is sufficient)
if _, err := gridClient.Tenant().Update(ctx, tenant); err != nil {
log.Fatalf("Failed to update tenant policy: %v", err)
}
fmt.Printf(" ✅ allowComplianceMode=true, maxRetentionYears=%d on tenant %s\n", maxYears, accountID)

tenantClient, err := client.NewTenantClient(buildOpts(endpoint, tenantUser, tenantPass, &accountID, skipSSL)...)
if err != nil {
log.Fatalf("Failed to create tenant client: %v", err)
}

// 4. Create a bucket with S3 Object Lock enabled.
bucketName := fmt.Sprintf("object-lock-demo-%s", time.Now().Format("20060102-150405"))
enabled := true
bucket := &models.Bucket{
Name: bucketName,
Region: "us-east-1",
EnableVersioning: &enabled, // S3 Object Lock requires versioning
S3ObjectLock: &models.BucketS3ObjectLockSettings{
Enabled: &enabled,
DefaultRetentionSetting: &models.BucketS3ObjectLockDefaultRetentionSettings{
Mode: "compliance",
Days: 30,
},
},
}

fmt.Printf("\n🏗️ Creating bucket %q with S3 Object Lock enabled...\n", bucketName)
created, err := tenantClient.Bucket().Create(ctx, bucket)
if err != nil {
log.Fatalf("Failed to create bucket: %v", err)
}
fmt.Printf(" ✅ Created %s in %s\n", created.Name, created.Region)

// 5. Read back per-bucket Object Lock settings via the dedicated sub-resource.
fmt.Println("\n🔍 Reading bucket Object Lock settings (/org/containers/<name>/object-lock)...")
settings, err := tenantClient.Bucket().GetObjectLock(ctx, bucketName)
if err != nil {
log.Fatalf("Failed to get bucket Object Lock settings: %v", err)
}
printBucketObjectLock(settings)
}

func buildOpts(endpoint, user, pass string, accountID *string, skipSSL bool) []client.ClientOption {
opts := []client.ClientOption{
client.WithEndpoint(endpoint),
client.WithCredentials(&models.Credentials{
Username: user,
Password: pass,
AccountId: accountID,
}),
}
if skipSSL {
opts = append(opts, client.WithSkipSSL())
}
return opts
}

func printGridSettings(label string, s *models.S3ObjectLock) {
fmt.Printf("\n📊 %s:\n", label)
fmt.Printf(" complianceEnabled: %s\n", boolStr(s.ComplianceEnabled))
fmt.Printf(" legacyComplianceEnabled: %s\n", boolStr(s.LegacyComplianceEnabled))
fmt.Printf(" createLegacyComplianceBuckets: %s\n", boolStr(s.CreateLegacyComplianceBuckets))
}

func printBucketObjectLock(s *models.BucketS3ObjectLockSettings) {
fmt.Printf(" enabled: %s\n", boolStr(s.Enabled))
if s.DefaultRetentionSetting == nil {
fmt.Println(" defaultRetentionSetting: (none)")
return
}
fmt.Println(" defaultRetentionSetting:")
fmt.Printf(" mode: %s\n", s.DefaultRetentionSetting.Mode)
if s.DefaultRetentionSetting.Days != 0 {
fmt.Printf(" days: %d\n", s.DefaultRetentionSetting.Days)
}
if s.DefaultRetentionSetting.Years != 0 {
fmt.Printf(" years: %d\n", s.DefaultRetentionSetting.Years)
}
}

func boolStr(b *bool) string {
if b == nil {
return "(unset)"
}
if *b {
return "true"
}
return "false"
}
Loading