Files
1Panel/agent/utils/cloud_storage/client/s3.go
ssongliu 1ab3da1fab fix: apply timeout to snapshot uploads (#13275)
* fix: apply timeout to snapshot uploads

* fix: honor snapshot upload timeouts for sftp and upyun
2026-07-16 11:21:26 +08:00

186 lines
4.4 KiB
Go

package client
import (
"context"
"crypto/tls"
"net/http"
"os"
"strings"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const (
s3DefaultTimeout = 30 * time.Second
s3TransferTimeout = 24 * time.Hour
)
type s3Client struct {
scType string
bucket string
client *minio.Client
}
func (s *s3Client) ctx(timeout time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout)
}
func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
endpoint := loadParamFromVars("endpoint", vars)
region := loadParamFromVars("region", vars)
bucket := loadParamFromVars("bucket", vars)
scType := loadParamFromVars("scType", vars)
if len(scType) == 0 {
scType = "Standard"
}
mode := loadParamFromVars("mode", vars)
if len(mode) == 0 {
mode = "virtual hosted"
}
lookupStyle := minio.BucketLookupDNS
if mode == "path" {
lookupStyle = minio.BucketLookupPath
}
ssl := strings.Split(endpoint, ":")[0]
secure := false
tlsConfig := &tls.Config{}
if ssl == "https" {
secure = true
tlsConfig.InsecureSkipVerify = true
}
var transport http.RoundTripper = &http.Transport{
TLSClientConfig: tlsConfig,
}
endpoint = strings.TrimPrefix(endpoint, ssl+"://")
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: secure,
Region: region,
BucketLookup: lookupStyle,
Transport: transport,
})
if err != nil {
return nil, err
}
return &s3Client{scType: scType, bucket: bucket, client: client}, nil
}
func (s s3Client) ListBuckets() ([]interface{}, error) {
ctx, cancel := s.ctx(s3DefaultTimeout)
defer cancel()
buckets, err := s.client.ListBuckets(ctx)
if err != nil {
return nil, err
}
var result []interface{}
for _, b := range buckets {
result = append(result, b.Name)
}
return result, nil
}
func (s s3Client) Exist(path string) (bool, error) {
ctx, cancel := s.ctx(s3DefaultTimeout)
defer cancel()
_, err := s.client.StatObject(ctx, s.bucket, path, minio.StatObjectOptions{})
if err != nil {
resp := minio.ToErrorResponse(err)
if resp.StatusCode == 404 {
return false, nil
}
return false, err
}
return true, nil
}
func (s *s3Client) Size(path string) (int64, error) {
ctx, cancel := s.ctx(s3DefaultTimeout)
defer cancel()
info, err := s.client.StatObject(ctx, s.bucket, path, minio.StatObjectOptions{})
if err != nil {
return 0, err
}
return info.Size, nil
}
func (s s3Client) Delete(path string) (bool, error) {
ctx, cancel := s.ctx(s3DefaultTimeout)
defer cancel()
if err := s.client.RemoveObject(ctx, s.bucket, path, minio.RemoveObjectOptions{}); err != nil {
return false, err
}
return true, nil
}
func (s s3Client) Upload(ctx context.Context, src, target string) (bool, error) {
fileInfo, err := os.Stat(src)
if err != nil {
return false, err
}
file, err := os.Open(src)
if err != nil {
return false, err
}
defer file.Close()
opts := minio.PutObjectOptions{
StorageClass: s.scType,
}
const maxParts = 10000
const defaultPartSize = 64 * 1024 * 1024 // 64 MiB
partSize := uint64(defaultPartSize)
if fileInfo.Size() > int64(maxParts)*int64(defaultPartSize) {
partSize = uint64(fileInfo.Size()) / (maxParts - 1)
}
opts.PartSize = partSize
if _, ok := ctx.Deadline(); !ok {
var cancel context.CancelFunc
ctx, cancel = s.ctx(s3TransferTimeout)
defer cancel()
}
if _, err := s.client.PutObject(ctx, s.bucket, target, file, fileInfo.Size(), opts); err != nil {
return false, err
}
return true, nil
}
func (s s3Client) Download(src, target string) (bool, error) {
if _, err := os.Stat(target); err == nil {
_ = os.Remove(target)
}
ctx, cancel := s.ctx(s3TransferTimeout)
defer cancel()
if err := s.client.FGetObject(ctx, s.bucket, src, target, minio.GetObjectOptions{}); err != nil {
os.Remove(target)
return false, err
}
return true, nil
}
func (s *s3Client) ListObjects(prefix string) ([]string, error) {
opts := minio.ListObjectsOptions{
Recursive: true,
Prefix: prefix,
}
var result []string
ctx, cancel := s.ctx(s3DefaultTimeout)
defer cancel()
for object := range s.client.ListObjects(ctx, s.bucket, opts) {
if object.Err != nil {
return result, object.Err
}
result = append(result, object.Key)
}
return result, nil
}