// Изменено: 2026-03-07 // S3 storage — загрузка и скачивание zip архивов с кодом функций. // Используем Ceph S3 compatible API (minio-go клиент умеет работать с любым S3). // Код загружается пользователем через REST API, хранится в S3, // builder скачивает его для сборки Docker образа. package s3 import ( "context" "fmt" "io" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) // Client — клиент для работы с S3. type Client struct { mc *minio.Client bucket string } // New создаёт клиент S3 и проверяет/создаёт бакет. func New(endpoint, accessKey, secretKey, bucket string, useSSL bool) (*Client, error) { mc, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), Secure: useSSL, }) if err != nil { return nil, fmt.Errorf("create minio client: %w", err) } return &Client{mc: mc, bucket: bucket}, nil } // EnsureBucket создаёт бакет если он не существует. // Вызывается при старте сервиса. func (c *Client) EnsureBucket(ctx context.Context) error { exists, err := c.mc.BucketExists(ctx, c.bucket) if err != nil { return fmt.Errorf("check bucket: %w", err) } if !exists { if err := c.mc.MakeBucket(ctx, c.bucket, minio.MakeBucketOptions{}); err != nil { return fmt.Errorf("create bucket: %w", err) } } return nil } // Upload загружает zip архив с кодом функции в S3. // Ключ: functions/{namespace}/{name}/{version}.zip // Возвращает ключ объекта для сохранения в CRD. func (c *Client) Upload(ctx context.Context, namespace, funcName, version string, r io.Reader, size int64) (string, error) { key := fmt.Sprintf("functions/%s/%s/%s.zip", namespace, funcName, version) _, err := c.mc.PutObject(ctx, c.bucket, key, r, size, minio.PutObjectOptions{ ContentType: "application/zip", }) if err != nil { return "", fmt.Errorf("upload function code: %w", err) } return key, nil } // Download скачивает zip архив с кодом функции из S3. // Используется builder'ом для сборки образа. func (c *Client) Download(ctx context.Context, key string) (io.ReadCloser, error) { obj, err := c.mc.GetObject(ctx, c.bucket, key, minio.GetObjectOptions{}) if err != nil { return nil, fmt.Errorf("download function code: %w", err) } return obj, nil } // Delete удаляет архив с кодом функции из S3. // Вызывается при удалении Function CRD. func (c *Client) Delete(ctx context.Context, key string) error { if err := c.mc.RemoveObject(ctx, c.bucket, key, minio.RemoveObjectOptions{}); err != nil { return fmt.Errorf("delete function code: %w", err) } return nil } // Bucket возвращает имя бакета, с которым работает клиент. func (c *Client) Bucket() string { return c.bucket } // UploadContext загружает tar.gz с контекстом сборки (Dockerfile + код) в S3. // Ключ: contexts/{namespace}/{name}/{version}.tar.gz // Kaniko читает этот tar.gz как build context (--context=s3://bucket/key). func (c *Client) UploadContext(ctx context.Context, namespace, funcName, version string, r io.Reader, size int64) (string, error) { key := fmt.Sprintf("contexts/%s/%s/%s.tar.gz", namespace, funcName, version) _, err := c.mc.PutObject(ctx, c.bucket, key, r, size, minio.PutObjectOptions{ ContentType: "application/gzip", }) if err != nil { return "", fmt.Errorf("upload build context: %w", err) } return key, nil }