298 lines
8.1 KiB
Go
298 lines
8.1 KiB
Go
package object_storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
madmin "github.com/minio/madmin-go/v3"
|
|
)
|
|
|
|
// Service handles MinIO object storage operations
|
|
type Service struct {
|
|
client *minio.Client
|
|
adminClient *madmin.AdminClient
|
|
logger *logger.Logger
|
|
endpoint string
|
|
accessKey string
|
|
secretKey string
|
|
}
|
|
|
|
// NewService creates a new MinIO service
|
|
func NewService(endpoint, accessKey, secretKey string, log *logger.Logger) (*Service, error) {
|
|
// Create MinIO client
|
|
minioClient, err := minio.New(endpoint, &minio.Options{
|
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
|
Secure: false, // Set to true if using HTTPS
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
|
}
|
|
|
|
// Create MinIO Admin client
|
|
adminClient, err := madmin.New(endpoint, accessKey, secretKey, false)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create MinIO admin client: %w", err)
|
|
}
|
|
|
|
return &Service{
|
|
client: minioClient,
|
|
adminClient: adminClient,
|
|
logger: log,
|
|
endpoint: endpoint,
|
|
accessKey: accessKey,
|
|
secretKey: secretKey,
|
|
}, nil
|
|
}
|
|
|
|
// Bucket represents a MinIO bucket
|
|
type Bucket struct {
|
|
Name string `json:"name"`
|
|
CreationDate time.Time `json:"creation_date"`
|
|
Size int64 `json:"size"` // Total size in bytes
|
|
Objects int64 `json:"objects"` // Number of objects
|
|
AccessPolicy string `json:"access_policy"` // private, public-read, public-read-write
|
|
}
|
|
|
|
// ListBuckets lists all buckets in MinIO
|
|
func (s *Service) ListBuckets(ctx context.Context) ([]*Bucket, error) {
|
|
buckets, err := s.client.ListBuckets(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list buckets: %w", err)
|
|
}
|
|
|
|
result := make([]*Bucket, 0, len(buckets))
|
|
for _, bucket := range buckets {
|
|
bucketInfo, err := s.getBucketInfo(ctx, bucket.Name)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to get bucket info", "bucket", bucket.Name, "error", err)
|
|
// Continue with basic info
|
|
result = append(result, &Bucket{
|
|
Name: bucket.Name,
|
|
CreationDate: bucket.CreationDate,
|
|
Size: 0,
|
|
Objects: 0,
|
|
AccessPolicy: "private",
|
|
})
|
|
continue
|
|
}
|
|
|
|
result = append(result, bucketInfo)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// getBucketInfo gets detailed information about a bucket
|
|
func (s *Service) getBucketInfo(ctx context.Context, bucketName string) (*Bucket, error) {
|
|
// Get bucket creation date
|
|
buckets, err := s.client.ListBuckets(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var creationDate time.Time
|
|
for _, b := range buckets {
|
|
if b.Name == bucketName {
|
|
creationDate = b.CreationDate
|
|
break
|
|
}
|
|
}
|
|
|
|
// Get bucket size and object count by listing objects
|
|
var size int64
|
|
var objects int64
|
|
|
|
// List objects in bucket to calculate size and count
|
|
objectCh := s.client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
|
|
Recursive: true,
|
|
})
|
|
|
|
for object := range objectCh {
|
|
if object.Err != nil {
|
|
s.logger.Warn("Error listing object", "bucket", bucketName, "error", object.Err)
|
|
continue
|
|
}
|
|
objects++
|
|
size += object.Size
|
|
}
|
|
|
|
return &Bucket{
|
|
Name: bucketName,
|
|
CreationDate: creationDate,
|
|
Size: size,
|
|
Objects: objects,
|
|
AccessPolicy: s.getBucketPolicy(ctx, bucketName),
|
|
}, nil
|
|
}
|
|
|
|
// getBucketPolicy gets the access policy for a bucket
|
|
func (s *Service) getBucketPolicy(ctx context.Context, bucketName string) string {
|
|
policy, err := s.client.GetBucketPolicy(ctx, bucketName)
|
|
if err != nil {
|
|
return "private"
|
|
}
|
|
|
|
// Parse policy JSON to determine access type
|
|
// For simplicity, check if policy allows public read
|
|
if policy != "" {
|
|
// Check if policy contains public read access
|
|
if strings.Contains(policy, "s3:GetObject") && strings.Contains(policy, "Principal") && strings.Contains(policy, "*") {
|
|
if strings.Contains(policy, "s3:PutObject") {
|
|
return "public-read-write"
|
|
}
|
|
return "public-read"
|
|
}
|
|
}
|
|
|
|
return "private"
|
|
}
|
|
|
|
|
|
// CreateBucket creates a new bucket
|
|
func (s *Service) CreateBucket(ctx context.Context, bucketName string) error {
|
|
err := s.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create bucket: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteBucket deletes a bucket
|
|
func (s *Service) DeleteBucket(ctx context.Context, bucketName string) error {
|
|
err := s.client.RemoveBucket(ctx, bucketName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete bucket: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetBucketStats gets statistics for a bucket
|
|
func (s *Service) GetBucketStats(ctx context.Context, bucketName string) (*Bucket, error) {
|
|
return s.getBucketInfo(ctx, bucketName)
|
|
}
|
|
|
|
// User represents a MinIO IAM user
|
|
type User struct {
|
|
AccessKey string `json:"access_key"`
|
|
Status string `json:"status"` // "enabled" or "disabled"
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ListUsers lists all IAM users in MinIO
|
|
func (s *Service) ListUsers(ctx context.Context) ([]*User, error) {
|
|
users, err := s.adminClient.ListUsers(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list users: %w", err)
|
|
}
|
|
|
|
result := make([]*User, 0, len(users))
|
|
for accessKey, userInfo := range users {
|
|
status := "enabled"
|
|
if userInfo.Status == madmin.AccountDisabled {
|
|
status = "disabled"
|
|
}
|
|
|
|
// MinIO doesn't provide creation date, use current time
|
|
result = append(result, &User{
|
|
AccessKey: accessKey,
|
|
Status: status,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CreateUser creates a new IAM user in MinIO
|
|
func (s *Service) CreateUser(ctx context.Context, accessKey, secretKey string) error {
|
|
err := s.adminClient.AddUser(ctx, accessKey, secretKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteUser deletes an IAM user from MinIO
|
|
func (s *Service) DeleteUser(ctx context.Context, accessKey string) error {
|
|
err := s.adminClient.RemoveUser(ctx, accessKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete user: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ServiceAccount represents a MinIO service account (access key)
|
|
type ServiceAccount struct {
|
|
AccessKey string `json:"access_key"`
|
|
SecretKey string `json:"secret_key,omitempty"` // Only returned on creation
|
|
ParentUser string `json:"parent_user"`
|
|
Expiration time.Time `json:"expiration,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ListServiceAccounts lists all service accounts in MinIO
|
|
func (s *Service) ListServiceAccounts(ctx context.Context) ([]*ServiceAccount, error) {
|
|
accounts, err := s.adminClient.ListServiceAccounts(ctx, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list service accounts: %w", err)
|
|
}
|
|
|
|
result := make([]*ServiceAccount, 0, len(accounts.Accounts))
|
|
for _, account := range accounts.Accounts {
|
|
var expiration time.Time
|
|
if account.Expiration != nil {
|
|
expiration = *account.Expiration
|
|
}
|
|
|
|
result = append(result, &ServiceAccount{
|
|
AccessKey: account.AccessKey,
|
|
ParentUser: account.ParentUser,
|
|
Expiration: expiration,
|
|
CreatedAt: time.Now(), // MinIO doesn't provide creation date
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CreateServiceAccount creates a new service account (access key) in MinIO
|
|
func (s *Service) CreateServiceAccount(ctx context.Context, parentUser string, policy string, expiration *time.Time) (*ServiceAccount, error) {
|
|
opts := madmin.AddServiceAccountReq{
|
|
TargetUser: parentUser,
|
|
}
|
|
if policy != "" {
|
|
opts.Policy = json.RawMessage(policy)
|
|
}
|
|
if expiration != nil {
|
|
opts.Expiration = expiration
|
|
}
|
|
|
|
creds, err := s.adminClient.AddServiceAccount(ctx, opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create service account: %w", err)
|
|
}
|
|
|
|
return &ServiceAccount{
|
|
AccessKey: creds.AccessKey,
|
|
SecretKey: creds.SecretKey,
|
|
ParentUser: parentUser,
|
|
Expiration: creds.Expiration,
|
|
CreatedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteServiceAccount deletes a service account from MinIO
|
|
func (s *Service) DeleteServiceAccount(ctx context.Context, accessKey string) error {
|
|
err := s.adminClient.DeleteServiceAccount(ctx, accessKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete service account: %w", err)
|
|
}
|
|
return nil
|
|
}
|