diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index eaff6d442..b318ee306 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/weed/s3api/s3api_bucket_handlers.go @@ -565,6 +565,31 @@ func (s3a *S3ApiServer) checkBucket(r *http.Request, bucket string) s3err.ErrorC return s3err.ErrNone } +// autoCreateBucket creates a bucket if it doesn't exist, setting the owner from the request context +func (s3a *S3ApiServer) autoCreateBucket(r *http.Request, bucket string) error { + currentIdentityId := s3_constants.GetIdentityNameFromContext(r) + fn := func(entry *filer_pb.Entry) { + if currentIdentityId != "" { + if entry.Extended == nil { + entry.Extended = make(map[string][]byte) + } + entry.Extended[s3_constants.AmzIdentityId] = []byte(currentIdentityId) + } + } + + if err := s3a.mkdir(s3a.option.BucketsPath, bucket, fn); err != nil { + return fmt.Errorf("failed to auto-create bucket %s: %w", bucket, err) + } + + // Remove bucket from negative cache after successful creation + if s3a.bucketConfigCache != nil { + s3a.bucketConfigCache.RemoveNegativeCache(bucket) + } + + glog.V(1).Infof("Auto-created bucket %s for identity %s", bucket, currentIdentityId) + return nil +} + func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool { // Check if user is properly authenticated as admin through IAM system if s3a.isUserAdmin(r) { diff --git a/weed/s3api/s3api_object_handlers_multipart.go b/weed/s3api/s3api_object_handlers_multipart.go index ba9886d66..eabd4fe7a 100644 --- a/weed/s3api/s3api_object_handlers_multipart.go +++ b/weed/s3api/s3api_object_handlers_multipart.go @@ -33,8 +33,16 @@ const ( func (s3a *S3ApiServer) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { bucket, object := s3_constants.GetBucketAndObject(r) - // Check if bucket exists before creating multipart upload - if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone { + // Check if bucket exists, and create it if it doesn't (auto-create bucket) + if err := s3a.checkBucket(r, bucket); err == s3err.ErrNoSuchBucket { + // Auto-create bucket if it doesn't exist + if mkdirErr := s3a.autoCreateBucket(r, bucket); mkdirErr != nil { + glog.Errorf("NewMultipartUploadHandler: %v", mkdirErr) + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } + } else if err != s3err.ErrNone { + // Other errors (like access denied) should still fail s3err.WriteErrorResponse(w, r, err) return } diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index c618405ca..0caf5d025 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -135,12 +135,19 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request) versioningState, err := s3a.getVersioningState(bucket) if err != nil { if errors.Is(err, filer_pb.ErrNotFound) { - s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket) + // Auto-create bucket if it doesn't exist + if mkdirErr := s3a.autoCreateBucket(r, bucket); mkdirErr != nil { + glog.Errorf("PutObjectHandler: %v", mkdirErr) + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } + // After creating the bucket, versioning state is empty (not configured) + versioningState = "" + } else { + glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return } - glog.Errorf("Error checking versioning status for bucket %s: %v", bucket, err) - s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) - return } versioningEnabled := (versioningState == s3_constants.VersioningEnabled)