|
|
@ -1,6 +1,7 @@ |
|
|
|
package s3api |
|
|
|
|
|
|
|
import ( |
|
|
|
"bytes" |
|
|
|
"context" |
|
|
|
"encoding/xml" |
|
|
|
"errors" |
|
|
@ -10,6 +11,7 @@ import ( |
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util" |
|
|
|
"math" |
|
|
|
"net/http" |
|
|
|
"strings" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer" |
|
|
@ -325,38 +327,155 @@ func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWr |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchLifecycleConfiguration) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
response := Lifecycle{} |
|
|
|
for prefix, internalTtl := range ttls { |
|
|
|
for locationPrefix, internalTtl := range ttls { |
|
|
|
ttl, _ := needle.ReadTTL(internalTtl) |
|
|
|
days := int(ttl.Minutes() / 60 / 24) |
|
|
|
if days == 0 { |
|
|
|
continue |
|
|
|
} |
|
|
|
prefix, found := strings.CutPrefix(locationPrefix, fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket)) |
|
|
|
if !found { |
|
|
|
continue |
|
|
|
} |
|
|
|
response.Rules = append(response.Rules, Rule{ |
|
|
|
Status: Enabled, Filter: Filter{ |
|
|
|
Prefix: Prefix{string: prefix, set: true}, |
|
|
|
set: true, |
|
|
|
}, |
|
|
|
ID: prefix, |
|
|
|
Status: Enabled, |
|
|
|
Prefix: Prefix{val: prefix, set: true}, |
|
|
|
Expiration: Expiration{Days: days, set: true}, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
writeSuccessResponseXML(w, r, response) |
|
|
|
} |
|
|
|
|
|
|
|
// PutBucketLifecycleConfigurationHandler Put Bucket Lifecycle configuration
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
|
|
|
|
func (s3a *S3ApiServer) PutBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
// collect parameters
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r) |
|
|
|
glog.V(3).Infof("PutBucketLifecycleConfigurationHandler %s", bucket) |
|
|
|
|
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone { |
|
|
|
s3err.WriteErrorResponse(w, r, err) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
lifeCycleConfig := Lifecycle{} |
|
|
|
if err := xmlDecoder(r.Body, &lifeCycleConfig, r.ContentLength); err != nil { |
|
|
|
glog.Warningf("PutBucketLifecycleConfigurationHandler xml decode: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil) |
|
|
|
if err != nil { |
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler read filer config: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
return |
|
|
|
} |
|
|
|
collectionName := s3a.getCollectionName(bucket) |
|
|
|
collectionTtls := fc.GetCollectionTtls(collectionName) |
|
|
|
changed := false |
|
|
|
|
|
|
|
for _, rule := range lifeCycleConfig.Rules { |
|
|
|
if rule.Status != Enabled { |
|
|
|
continue |
|
|
|
} |
|
|
|
var rulePrefix string |
|
|
|
switch { |
|
|
|
case rule.Filter.Prefix.set: |
|
|
|
rulePrefix = rule.Filter.Prefix.val |
|
|
|
case rule.Prefix.set: |
|
|
|
rulePrefix = rule.Prefix.val |
|
|
|
case !rule.Expiration.Date.IsZero() || rule.Transition.Days > 0 || !rule.Transition.Date.IsZero(): |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if rule.Expiration.Days == 0 { |
|
|
|
continue |
|
|
|
} |
|
|
|
|
|
|
|
locConf := &filer_pb.FilerConf_PathConf{ |
|
|
|
LocationPrefix: fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, rulePrefix), |
|
|
|
Collection: collectionName, |
|
|
|
Ttl: fmt.Sprintf("%dd", rule.Expiration.Days), |
|
|
|
} |
|
|
|
if ttl, ok := collectionTtls[locConf.LocationPrefix]; ok && ttl == locConf.Ttl { |
|
|
|
continue |
|
|
|
} |
|
|
|
if err := fc.AddLocationConf(locConf); err != nil { |
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler add location config: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
return |
|
|
|
} |
|
|
|
changed = true |
|
|
|
} |
|
|
|
|
|
|
|
if changed { |
|
|
|
var buf bytes.Buffer |
|
|
|
if err := fc.ToText(&buf); err != nil { |
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler save config to text: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
} |
|
|
|
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { |
|
|
|
return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes()) |
|
|
|
}); err != nil { |
|
|
|
glog.Errorf("PutBucketLifecycleConfigurationHandler save config inside filer: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
writeSuccessResponseEmpty(w, r) |
|
|
|
} |
|
|
|
|
|
|
|
// DeleteBucketLifecycleHandler Delete Bucket Lifecycle
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html
|
|
|
|
func (s3a *S3ApiServer) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
// collect parameters
|
|
|
|
bucket, _ := s3_constants.GetBucketAndObject(r) |
|
|
|
glog.V(3).Infof("DeleteBucketLifecycleHandler %s", bucket) |
|
|
|
|
|
|
|
s3err.WriteEmptyResponse(w, r, http.StatusNoContent) |
|
|
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone { |
|
|
|
s3err.WriteErrorResponse(w, r, err) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil) |
|
|
|
if err != nil { |
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler read filer config: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
return |
|
|
|
} |
|
|
|
collectionTtls := fc.GetCollectionTtls(s3a.getCollectionName(bucket)) |
|
|
|
changed := false |
|
|
|
for prefix, ttl := range collectionTtls { |
|
|
|
bucketPrefix := fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket) |
|
|
|
if strings.HasPrefix(prefix, bucketPrefix) && strings.HasSuffix(ttl, "d") { |
|
|
|
fc.DeleteLocationConf(prefix) |
|
|
|
changed = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if changed { |
|
|
|
var buf bytes.Buffer |
|
|
|
if err := fc.ToText(&buf); err != nil { |
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler save config to text: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
} |
|
|
|
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { |
|
|
|
return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes()) |
|
|
|
}); err != nil { |
|
|
|
glog.Errorf("DeleteBucketLifecycleHandler save config inside filer: %s", err) |
|
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
s3err.WriteEmptyResponse(w, r, http.StatusNoContent) |
|
|
|
} |
|
|
|
|
|
|
|
// GetBucketLocationHandler Get bucket location
|
|
|
|