mirror of https://github.com/minio/minio.git
Merge pull request #164 from harshavardhana/pr_out_implement_bucket_policy_handler
This commit is contained in:
commit
4ce180bb72
|
@ -0,0 +1,235 @@
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
"github.com/minio-io/minio/pkg/utils/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (server *minioApi) putBucketPolicyHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
|
||||||
|
policy, ok := policy.Parsepolicy(req.Body)
|
||||||
|
if ok == false {
|
||||||
|
error := errorCodeError(InvalidPolicyDocument)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := server.storage.StoreBucketPolicy(bucket, policy)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
{
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
writeCommonHeaders(w, getContentString(acceptsContentType))
|
||||||
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchBucket)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BackendCorrupted:
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) getBucketPolicyHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
|
||||||
|
p, err := server.storage.GetBucketPolicy(bucket)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
{
|
||||||
|
responsePolicy, ret := json.Marshal(p)
|
||||||
|
if ret != nil {
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
writeCommonHeaders(w, getContentString(acceptsContentType))
|
||||||
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
w.Write(responsePolicy)
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchBucket)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketPolicyNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchBucketPolicy)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BackendCorrupted:
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
resources := getBucketResources(req.URL.Query())
|
||||||
|
if resources.policy == true {
|
||||||
|
server.getBucketPolicyHandler(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
objects, isTruncated, err := server.storage.ListObjects(bucket, resources.prefix, 1000)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil: // success
|
||||||
|
{
|
||||||
|
response := generateObjectsListResult(bucket, objects, isTruncated)
|
||||||
|
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchBucket)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
// Embed error log on server side
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ObjectNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchKey)
|
||||||
|
errorResponse := getErrorResponse(error, resources.prefix)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
buckets, err := server.storage.ListBuckets()
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
{
|
||||||
|
response := generateBucketsListResult(buckets)
|
||||||
|
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, "")
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BackendCorrupted:
|
||||||
|
{
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, "")
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
err := server.storage.StoreBucket(bucket)
|
||||||
|
|
||||||
|
resources := getBucketResources(req.URL.Query())
|
||||||
|
if resources.policy == true {
|
||||||
|
server.putBucketPolicyHandler(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
{
|
||||||
|
w.Header().Set("Server", "Minio")
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketExists:
|
||||||
|
{
|
||||||
|
error := errorCodeError(BucketAlreadyExists)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
// Embed errors log on serve side
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,11 +62,12 @@ type Owner struct {
|
||||||
|
|
||||||
var unimplementedBucketResourceNames = map[string]bool{
|
var unimplementedBucketResourceNames = map[string]bool{
|
||||||
"acl": true,
|
"acl": true,
|
||||||
|
"cors": true,
|
||||||
"lifecycle": true,
|
"lifecycle": true,
|
||||||
"policy": true,
|
|
||||||
"location": true,
|
"location": true,
|
||||||
"logging": true,
|
"logging": true,
|
||||||
"notification": true,
|
"notification": true,
|
||||||
|
"tagging": true,
|
||||||
"versions": true,
|
"versions": true,
|
||||||
"requestPayment": true,
|
"requestPayment": true,
|
||||||
"versioning": true,
|
"versioning": true,
|
||||||
|
|
|
@ -59,6 +59,8 @@ const (
|
||||||
RequestTimeTooSkewed
|
RequestTimeTooSkewed
|
||||||
SignatureDoesNotMatch
|
SignatureDoesNotMatch
|
||||||
TooManyBuckets
|
TooManyBuckets
|
||||||
|
InvalidPolicyDocument
|
||||||
|
NoSuchBucketPolicy
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorCodeResponse = map[int]Error{
|
var errorCodeResponse = map[int]Error{
|
||||||
|
@ -167,6 +169,16 @@ var errorCodeResponse = map[int]Error{
|
||||||
Description: "You have attempted to create more buckets than allowed.",
|
Description: "You have attempted to create more buckets than allowed.",
|
||||||
HttpStatusCode: http.StatusBadRequest,
|
HttpStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
InvalidPolicyDocument: {
|
||||||
|
Code: "InvalidPolicyDocument",
|
||||||
|
Description: "The content of the form does not meet the conditions specified in the policy document.",
|
||||||
|
HttpStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
NoSuchBucketPolicy: {
|
||||||
|
Code: "NoSuchBucketPolicy",
|
||||||
|
Description: "The specified bucket does not have a bucket policy.",
|
||||||
|
HttpStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// errorCodeError provides errorCode to Error. It returns empty if
|
// errorCodeError provides errorCode to Error. It returns empty if
|
||||||
|
|
|
@ -1,376 +0,0 @@
|
||||||
/*
|
|
||||||
* Mini Object Storage, (C) 2014 Minio, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package minioapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
|
||||||
"github.com/minio-io/minio/pkg/utils/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dateFormat = "2006-01-02T15:04:05.000Z"
|
|
||||||
)
|
|
||||||
|
|
||||||
type minioApi struct {
|
|
||||||
storage mstorage.Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
// No encoder interface exists, so we create one.
|
|
||||||
type encoder interface {
|
|
||||||
Encode(v interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpHandler(storage mstorage.Storage) http.Handler {
|
|
||||||
mux := mux.NewRouter()
|
|
||||||
var api = minioApi{}
|
|
||||||
api.storage = storage
|
|
||||||
|
|
||||||
var conf = config.Config{}
|
|
||||||
if err := conf.SetupConfig(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.HandleFunc("/", api.listBucketsHandler).Methods("GET")
|
|
||||||
mux.HandleFunc("/{bucket}", api.listObjectsHandler).Methods("GET")
|
|
||||||
mux.HandleFunc("/{bucket}", api.putBucketHandler).Methods("PUT")
|
|
||||||
mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET")
|
|
||||||
mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD")
|
|
||||||
mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT")
|
|
||||||
|
|
||||||
return validateHandler(conf, ignoreUnimplementedResources(mux))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
prefix, ok := vars["prefix"]
|
|
||||||
if !ok {
|
|
||||||
prefix = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
buckets, err := server.storage.ListBuckets(prefix)
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil:
|
|
||||||
{
|
|
||||||
response := generateBucketsListResult(buckets)
|
|
||||||
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(InvalidBucketName)
|
|
||||||
errorResponse := getErrorResponse(error, prefix)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, prefix)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BackendCorrupted:
|
|
||||||
{
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, prefix)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
prefix, ok := vars["prefix"]
|
|
||||||
if !ok {
|
|
||||||
prefix = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
|
|
||||||
objects, isTruncated, err := server.storage.ListObjects(bucket, prefix, 1000)
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil: // success
|
|
||||||
{
|
|
||||||
response := generateObjectsListResult(bucket, objects, isTruncated)
|
|
||||||
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNotFound:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchBucket)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
// Embed error log on server side
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(InvalidBucketName)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ObjectNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchKey)
|
|
||||||
errorResponse := getErrorResponse(error, prefix)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) putBucketHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
err := server.storage.StoreBucket(bucket)
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil:
|
|
||||||
{
|
|
||||||
w.Header().Set("Server", "Minio")
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
}
|
|
||||||
case mstorage.BucketNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(InvalidBucketName)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketExists:
|
|
||||||
{
|
|
||||||
error := errorCodeError(BucketAlreadyExists)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
// Embed errors log on serve side
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
object := vars["object"]
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
|
|
||||||
metadata, err := server.storage.GetObjectMetadata(bucket, object)
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil: // success
|
|
||||||
{
|
|
||||||
log.Println("Found: " + bucket + "#" + object)
|
|
||||||
writeObjectHeaders(w, metadata)
|
|
||||||
if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case mstorage.ObjectNotFound:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchKey)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ObjectNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchKey)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(InvalidBucketName)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
// Embed errors log on serve side
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
object := vars["object"]
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
|
|
||||||
metadata, err := server.storage.GetObjectMetadata(bucket, object)
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil:
|
|
||||||
writeObjectHeaders(w, metadata)
|
|
||||||
case mstorage.ObjectNotFound:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchKey)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ObjectNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchKey)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
// Embed error log on server side
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
vars := mux.Vars(req)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
object := vars["object"]
|
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
|
||||||
|
|
||||||
err := server.storage.StoreObject(bucket, object, "", req.Body)
|
|
||||||
switch err := err.(type) {
|
|
||||||
case nil:
|
|
||||||
w.Header().Set("Server", "Minio")
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
case mstorage.ImplementationError:
|
|
||||||
{
|
|
||||||
// Embed error log on server side
|
|
||||||
log.Println(err)
|
|
||||||
error := errorCodeError(InternalError)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNotFound:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NoSuchBucket)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.BucketNameInvalid:
|
|
||||||
{
|
|
||||||
error := errorCodeError(InvalidBucketName)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
case mstorage.ObjectExists:
|
|
||||||
{
|
|
||||||
error := errorCodeError(NotImplemented)
|
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
|
||||||
w.WriteHeader(error.HttpStatusCode)
|
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse {
|
|
||||||
var listbuckets []*Bucket
|
|
||||||
var data = BucketListResponse{}
|
|
||||||
var owner = Owner{}
|
|
||||||
|
|
||||||
owner.ID = "minio"
|
|
||||||
owner.DisplayName = "minio"
|
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
|
||||||
var listbucket = &Bucket{}
|
|
||||||
listbucket.Name = bucket.Name
|
|
||||||
listbucket.CreationDate = bucket.Created.Format(dateFormat)
|
|
||||||
listbuckets = append(listbuckets, listbucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Owner = owner
|
|
||||||
data.Buckets.Bucket = listbuckets
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a set of objects and prepares the objects for serialization
|
|
||||||
// input:
|
|
||||||
// bucket name
|
|
||||||
// array of object metadata
|
|
||||||
// results truncated flag
|
|
||||||
//
|
|
||||||
// output:
|
|
||||||
// populated struct that can be serialized to match xml and json api spec output
|
|
||||||
func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse {
|
|
||||||
var contents []*Item
|
|
||||||
var owner = Owner{}
|
|
||||||
var data = ObjectListResponse{}
|
|
||||||
|
|
||||||
owner.ID = "minio"
|
|
||||||
owner.DisplayName = "minio"
|
|
||||||
|
|
||||||
for _, object := range objects {
|
|
||||||
var content = &Item{}
|
|
||||||
content.Key = object.Key
|
|
||||||
content.LastModified = object.Created.Format(dateFormat)
|
|
||||||
content.ETag = object.ETag
|
|
||||||
content.Size = object.Size
|
|
||||||
content.StorageClass = "STANDARD"
|
|
||||||
content.Owner = owner
|
|
||||||
contents = append(contents, content)
|
|
||||||
}
|
|
||||||
data.Name = bucket
|
|
||||||
data.Contents = contents
|
|
||||||
data.MaxKeys = MAX_OBJECT_LIST
|
|
||||||
data.IsTruncated = isTruncated
|
|
||||||
return data
|
|
||||||
}
|
|
|
@ -218,7 +218,7 @@ func (s *MySuite) TestPutBucket(c *C) {
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
buckets, err := storage.ListBuckets("bucket")
|
buckets, err := storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 0)
|
c.Assert(len(buckets), Equals, 0)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ func (s *MySuite) TestPutBucket(c *C) {
|
||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
// check bucket exists
|
// check bucket exists
|
||||||
buckets, err = storage.ListBuckets("bucket")
|
buckets, err = storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 1)
|
c.Assert(len(buckets), Equals, 1)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(buckets[0].Name, Equals, "bucket")
|
c.Assert(buckets[0].Name, Equals, "bucket")
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (server *minioApi) getObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var object, bucket string
|
||||||
|
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket = vars["bucket"]
|
||||||
|
object = vars["object"]
|
||||||
|
|
||||||
|
metadata, err := server.storage.GetObjectMetadata(bucket, object)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil: // success
|
||||||
|
{
|
||||||
|
log.Println("Found: " + bucket + "#" + object)
|
||||||
|
writeObjectHeaders(w, metadata)
|
||||||
|
if _, err := server.storage.CopyObjectToWriter(w, bucket, object); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case mstorage.ObjectNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchKey)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ObjectNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchKey)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
// Embed errors log on serve side
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) headObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var object, bucket string
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
bucket = vars["bucket"]
|
||||||
|
object = vars["object"]
|
||||||
|
|
||||||
|
metadata, err := server.storage.GetObjectMetadata(bucket, object)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
writeObjectHeaders(w, metadata)
|
||||||
|
case mstorage.ObjectNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchKey)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ObjectNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchKey)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
// Embed error log on server side
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *minioApi) putObjectHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var object, bucket string
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
acceptsContentType := getContentType(req)
|
||||||
|
bucket = vars["bucket"]
|
||||||
|
object = vars["object"]
|
||||||
|
|
||||||
|
err := server.storage.StoreObject(bucket, object, "", req.Body)
|
||||||
|
switch err := err.(type) {
|
||||||
|
case nil:
|
||||||
|
w.Header().Set("Server", "Minio")
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
case mstorage.ImplementationError:
|
||||||
|
{
|
||||||
|
// Embed error log on server side
|
||||||
|
log.Println(err)
|
||||||
|
error := errorCodeError(InternalError)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNotFound:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NoSuchBucket)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.BucketNameInvalid:
|
||||||
|
{
|
||||||
|
error := errorCodeError(InvalidBucketName)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
case mstorage.ObjectExists:
|
||||||
|
{
|
||||||
|
error := errorCodeError(NotImplemented)
|
||||||
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
w.WriteHeader(error.HttpStatusCode)
|
||||||
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Mini Object Storage, (C) 2015 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pHandler struct {
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyHandler(h http.Handler) http.Handler {
|
||||||
|
return pHandler{h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bucketResources struct {
|
||||||
|
prefix string
|
||||||
|
marker string
|
||||||
|
maxkeys int
|
||||||
|
policy bool
|
||||||
|
delimiter string
|
||||||
|
// uploads bool - TODO implemented with multipart support
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBucketResources(values url.Values) (v bucketResources) {
|
||||||
|
for key, value := range values {
|
||||||
|
switch true {
|
||||||
|
case key == "prefix":
|
||||||
|
v.prefix = value[0]
|
||||||
|
case key == "marker":
|
||||||
|
v.marker = value[0]
|
||||||
|
case key == "maxkeys":
|
||||||
|
v.maxkeys, _ = strconv.Atoi(value[0])
|
||||||
|
case key == "policy":
|
||||||
|
v.policy = true
|
||||||
|
case key == "delimiter":
|
||||||
|
v.delimiter = value[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse {
|
||||||
|
var listbuckets []*Bucket
|
||||||
|
var data = BucketListResponse{}
|
||||||
|
var owner = Owner{}
|
||||||
|
|
||||||
|
owner.ID = "minio"
|
||||||
|
owner.DisplayName = "minio"
|
||||||
|
|
||||||
|
for _, bucket := range buckets {
|
||||||
|
var listbucket = &Bucket{}
|
||||||
|
listbucket.Name = bucket.Name
|
||||||
|
listbucket.CreationDate = bucket.Created.Format(dateFormat)
|
||||||
|
listbuckets = append(listbuckets, listbucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Owner = owner
|
||||||
|
data.Buckets.Bucket = listbuckets
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// takes a set of objects and prepares the objects for serialization
|
||||||
|
// input:
|
||||||
|
// bucket name
|
||||||
|
// array of object metadata
|
||||||
|
// results truncated flag
|
||||||
|
//
|
||||||
|
// output:
|
||||||
|
// populated struct that can be serialized to match xml and json api spec output
|
||||||
|
func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, isTruncated bool) ObjectListResponse {
|
||||||
|
var contents []*Item
|
||||||
|
var owner = Owner{}
|
||||||
|
var data = ObjectListResponse{}
|
||||||
|
|
||||||
|
owner.ID = "minio"
|
||||||
|
owner.DisplayName = "minio"
|
||||||
|
|
||||||
|
for _, object := range objects {
|
||||||
|
var content = &Item{}
|
||||||
|
content.Key = object.Key
|
||||||
|
content.LastModified = object.Created.Format(dateFormat)
|
||||||
|
content.ETag = object.ETag
|
||||||
|
content.Size = object.Size
|
||||||
|
content.StorageClass = "STANDARD"
|
||||||
|
content.Owner = owner
|
||||||
|
contents = append(contents, content)
|
||||||
|
}
|
||||||
|
data.Name = bucket
|
||||||
|
data.Contents = contents
|
||||||
|
data.MaxKeys = MAX_OBJECT_LIST
|
||||||
|
data.IsTruncated = isTruncated
|
||||||
|
return data
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Mini Object Storage, (C) 2014 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minioapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
"github.com/minio-io/minio/pkg/utils/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dateFormat = "2006-01-02T15:04:05.000Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
type minioApi struct {
|
||||||
|
storage mstorage.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// No encoder interface exists, so we create one.
|
||||||
|
type encoder interface {
|
||||||
|
Encode(v interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpHandler(storage mstorage.Storage) http.Handler {
|
||||||
|
mux := mux.NewRouter()
|
||||||
|
var api = minioApi{}
|
||||||
|
api.storage = storage
|
||||||
|
|
||||||
|
var conf = config.Config{}
|
||||||
|
if err := conf.SetupConfig(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/", api.listBucketsHandler).Methods("GET")
|
||||||
|
mux.HandleFunc("/{bucket}", api.listObjectsHandler).Methods("GET")
|
||||||
|
mux.HandleFunc("/{bucket}", api.putBucketHandler).Methods("PUT")
|
||||||
|
mux.HandleFunc("/{bucket}/{object:.*}", api.getObjectHandler).Methods("GET")
|
||||||
|
mux.HandleFunc("/{bucket}/{object:.*}", api.headObjectHandler).Methods("HEAD")
|
||||||
|
mux.HandleFunc("/{bucket}/{object:.*}", api.putObjectHandler).Methods("PUT")
|
||||||
|
|
||||||
|
return validateHandler(conf, ignoreUnimplementedResources(mux))
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
"github.com/minio-io/minio/pkg/utils/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type storage struct {
|
type storage struct {
|
||||||
|
@ -57,13 +58,7 @@ func start(ctrlChannel <-chan string, errorChannel chan<- error) {
|
||||||
|
|
||||||
// Bucket Operations
|
// Bucket Operations
|
||||||
|
|
||||||
func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, error) {
|
func (storage *storage) ListBuckets() ([]mstorage.BucketMetadata, error) {
|
||||||
if prefix != "" {
|
|
||||||
if mstorage.IsValidBucket(prefix) == false {
|
|
||||||
return []mstorage.BucketMetadata{}, mstorage.BucketNameInvalid{Bucket: prefix}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(storage.root)
|
files, err := ioutil.ReadDir(storage.root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []mstorage.BucketMetadata{}, mstorage.EmbedError("bucket", "", err)
|
return []mstorage.BucketMetadata{}, mstorage.EmbedError("bucket", "", err)
|
||||||
|
@ -74,14 +69,12 @@ func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, e
|
||||||
if !file.IsDir() {
|
if !file.IsDir() {
|
||||||
return []mstorage.BucketMetadata{}, mstorage.BackendCorrupted{Path: storage.root}
|
return []mstorage.BucketMetadata{}, mstorage.BackendCorrupted{Path: storage.root}
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(file.Name(), prefix) {
|
|
||||||
metadata := mstorage.BucketMetadata{
|
metadata := mstorage.BucketMetadata{
|
||||||
Name: file.Name(),
|
Name: file.Name(),
|
||||||
Created: file.ModTime(), // TODO - provide real created time
|
Created: file.ModTime(), // TODO - provide real created time
|
||||||
}
|
}
|
||||||
metadataList = append(metadataList, metadata)
|
metadataList = append(metadataList, metadata)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return metadataList, nil
|
return metadataList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +105,86 @@ func (storage *storage) StoreBucket(bucket string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (storage *storage) GetBucketPolicy(bucket string) (interface{}, error) {
|
||||||
|
storage.writeLock.Lock()
|
||||||
|
defer storage.writeLock.Unlock()
|
||||||
|
|
||||||
|
var p policy.BucketPolicy
|
||||||
|
// verify bucket path legal
|
||||||
|
if mstorage.IsValidBucket(bucket) == false {
|
||||||
|
return policy.BucketPolicy{}, mstorage.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bucket path
|
||||||
|
bucketDir := path.Join(storage.root, bucket)
|
||||||
|
// check if bucket exists
|
||||||
|
if _, err := os.Stat(bucketDir); err != nil {
|
||||||
|
return policy.BucketPolicy{}, mstorage.BucketNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get policy path
|
||||||
|
bucketPolicy := path.Join(storage.root, bucket+"_policy.json")
|
||||||
|
filestat, err := os.Stat(bucketPolicy)
|
||||||
|
if filestat.IsDir() {
|
||||||
|
return policy.BucketPolicy{}, mstorage.BackendCorrupted{Path: bucketPolicy}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return policy.BucketPolicy{}, mstorage.BucketPolicyNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(bucketPolicy, os.O_RDONLY, 0666)
|
||||||
|
defer file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return policy.BucketPolicy{}, mstorage.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
encoder := json.NewDecoder(file)
|
||||||
|
err = encoder.Decode(&p)
|
||||||
|
if err != nil {
|
||||||
|
return policy.BucketPolicy{}, mstorage.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage *storage) StoreBucketPolicy(bucket string, policy interface{}) error {
|
||||||
|
storage.writeLock.Lock()
|
||||||
|
defer storage.writeLock.Unlock()
|
||||||
|
|
||||||
|
// verify bucket path legal
|
||||||
|
if mstorage.IsValidBucket(bucket) == false {
|
||||||
|
return mstorage.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bucket path
|
||||||
|
bucketDir := path.Join(storage.root, bucket)
|
||||||
|
// check if bucket exists
|
||||||
|
if _, err := os.Stat(bucketDir); err != nil {
|
||||||
|
return mstorage.BucketNotFound{
|
||||||
|
Bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get policy path
|
||||||
|
bucketPolicy := path.Join(storage.root, bucket+"_policy.json")
|
||||||
|
filestat, _ := os.Stat(bucketPolicy)
|
||||||
|
if filestat.IsDir() {
|
||||||
|
return mstorage.BackendCorrupted{Path: bucketPolicy}
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(bucketPolicy, os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
defer file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return mstorage.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
err = encoder.Encode(policy)
|
||||||
|
if err != nil {
|
||||||
|
return mstorage.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Object Operations
|
// Object Operations
|
||||||
|
|
||||||
func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error) {
|
func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error) {
|
||||||
|
@ -145,6 +218,10 @@ func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file, err := os.Open(objectPath)
|
file, err := os.Open(objectPath)
|
||||||
|
defer file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return 0, mstorage.EmbedError(bucket, object, err)
|
||||||
|
}
|
||||||
count, err := io.Copy(w, file)
|
count, err := io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return count, mstorage.EmbedError(bucket, object, err)
|
return count, mstorage.EmbedError(bucket, object, err)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
mstorage "github.com/minio-io/minio/pkg/storage"
|
||||||
|
"github.com/minio-io/minio/pkg/utils/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type storage struct {
|
type storage struct {
|
||||||
|
@ -57,6 +58,14 @@ func (storage *storage) CopyObjectToWriter(w io.Writer, bucket string, object st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (storage *storage) StoreBucketPolicy(bucket string, policy interface{}) error {
|
||||||
|
return mstorage.ApiNotImplemented{Api: "PutBucketPolicy"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage *storage) GetBucketPolicy(bucket string) (interface{}, error) {
|
||||||
|
return policy.BucketPolicy{}, mstorage.ApiNotImplemented{Api: "GetBucketPolicy"}
|
||||||
|
}
|
||||||
|
|
||||||
func (storage *storage) StoreObject(bucket, key, contentType string, data io.Reader) error {
|
func (storage *storage) StoreObject(bucket, key, contentType string, data io.Reader) error {
|
||||||
objectKey := bucket + ":" + key
|
objectKey := bucket + ":" + key
|
||||||
|
|
||||||
|
@ -145,13 +154,11 @@ func (b ByBucketName) Len() int { return len(b) }
|
||||||
func (b ByBucketName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
func (b ByBucketName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||||
func (b ByBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
func (b ByBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
||||||
|
|
||||||
func (storage *storage) ListBuckets(prefix string) ([]mstorage.BucketMetadata, error) {
|
func (storage *storage) ListBuckets() ([]mstorage.BucketMetadata, error) {
|
||||||
var results []mstorage.BucketMetadata
|
var results []mstorage.BucketMetadata
|
||||||
for key, bucket := range storage.bucketdata {
|
for _, bucket := range storage.bucketdata {
|
||||||
if strings.HasPrefix(key, prefix) {
|
|
||||||
results = append(results, bucket.metadata)
|
results = append(results, bucket.metadata)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
sort.Sort(ByBucketName(results))
|
sort.Sort(ByBucketName(results))
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,10 @@ import (
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
// Bucket Operations
|
// Bucket Operations
|
||||||
ListBuckets(prefix string) ([]BucketMetadata, error)
|
ListBuckets() ([]BucketMetadata, error)
|
||||||
StoreBucket(bucket string) error
|
StoreBucket(bucket string) error
|
||||||
|
StoreBucketPolicy(bucket string, policy interface{}) error
|
||||||
|
GetBucketPolicy(bucket string) (interface{}, error)
|
||||||
|
|
||||||
// Object Operations
|
// Object Operations
|
||||||
CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error)
|
CopyObjectToWriter(w io.Writer, bucket string, object string) (int64, error)
|
||||||
|
|
|
@ -183,7 +183,7 @@ func testListBuckets(c *C, create func() Storage) {
|
||||||
storage := create()
|
storage := create()
|
||||||
|
|
||||||
// test empty list
|
// test empty list
|
||||||
buckets, err := storage.ListBuckets("")
|
buckets, err := storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 0)
|
c.Assert(len(buckets), Equals, 0)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ func testListBuckets(c *C, create func() Storage) {
|
||||||
err = storage.StoreBucket("bucket1")
|
err = storage.StoreBucket("bucket1")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets("")
|
buckets, err = storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 1)
|
c.Assert(len(buckets), Equals, 1)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
@ -199,20 +199,16 @@ func testListBuckets(c *C, create func() Storage) {
|
||||||
err = storage.StoreBucket("bucket2")
|
err = storage.StoreBucket("bucket2")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets("")
|
buckets, err = storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 2)
|
c.Assert(len(buckets), Equals, 2)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// add three and test exists + prefix
|
// add three and test exists + prefix
|
||||||
err = storage.StoreBucket("bucket22")
|
err = storage.StoreBucket("bucket22")
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets("")
|
buckets, err = storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 3)
|
c.Assert(len(buckets), Equals, 3)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets("bucket2")
|
|
||||||
c.Assert(len(buckets), Equals, 2)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testListBucketsOrder(c *C, create func() Storage) {
|
func testListBucketsOrder(c *C, create func() Storage) {
|
||||||
|
@ -224,7 +220,7 @@ func testListBucketsOrder(c *C, create func() Storage) {
|
||||||
storage.StoreBucket("bucket1")
|
storage.StoreBucket("bucket1")
|
||||||
storage.StoreBucket("bucket2")
|
storage.StoreBucket("bucket2")
|
||||||
|
|
||||||
buckets, err := storage.ListBuckets("bucket")
|
buckets, err := storage.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 2)
|
c.Assert(len(buckets), Equals, 2)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(buckets[0].Name, Equals, "bucket1")
|
c.Assert(buckets[0].Name, Equals, "bucket1")
|
||||||
|
|
|
@ -30,6 +30,10 @@ type ObjectExists struct {
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApiNotImplemented struct {
|
||||||
|
Api string
|
||||||
|
}
|
||||||
|
|
||||||
type ObjectNotFound GenericObjectError
|
type ObjectNotFound GenericObjectError
|
||||||
|
|
||||||
type GenericBucketError struct {
|
type GenericBucketError struct {
|
||||||
|
@ -68,15 +72,24 @@ func EmbedError(bucket, object string, err error) ImplementationError {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackendCorrupted BackendError
|
type BackendCorrupted BackendError
|
||||||
|
type BucketPolicyNotFound GenericBucketError
|
||||||
type BucketNameInvalid GenericBucketError
|
type BucketNameInvalid GenericBucketError
|
||||||
type BucketExists GenericBucketError
|
type BucketExists GenericBucketError
|
||||||
type BucketNotFound GenericBucketError
|
type BucketNotFound GenericBucketError
|
||||||
type ObjectNameInvalid GenericObjectError
|
type ObjectNameInvalid GenericObjectError
|
||||||
|
|
||||||
|
func (self BucketPolicyNotFound) Error() string {
|
||||||
|
return "Bucket policy not found for: " + self.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
func (self ObjectNotFound) Error() string {
|
func (self ObjectNotFound) Error() string {
|
||||||
return "Object not Found: " + self.Bucket + "#" + self.Object
|
return "Object not Found: " + self.Bucket + "#" + self.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self ApiNotImplemented) Error() string {
|
||||||
|
return "Api not implemented: " + self.Api
|
||||||
|
}
|
||||||
|
|
||||||
func (self ObjectExists) Error() string {
|
func (self ObjectExists) Error() string {
|
||||||
return "Object exists: " + self.Bucket + "#" + self.Key
|
return "Object exists: " + self.Bucket + "#" + self.Key
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For 0000-00-00 Date type
|
||||||
|
type Date struct {
|
||||||
|
Year int16
|
||||||
|
Month byte
|
||||||
|
Day byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Date) String() string {
|
||||||
|
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if date is 0000-00-00
|
||||||
|
func (d Date) IsZero() bool {
|
||||||
|
return d.Day == 0 && d.Month == 0 && d.Year == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string date in format YYYY-MM-DD to Date.
|
||||||
|
// Leading and trailing spaces are ignored. If format is invalid returns zero.
|
||||||
|
func ParseDate(str string) (d Date, err error) {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
if str == "0000-00-00" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
y, m, n int
|
||||||
|
)
|
||||||
|
if len(str) != 10 || str[4] != '-' || str[7] != '-' {
|
||||||
|
err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if y, err = strconv.Atoi(str[0:4]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m, err = strconv.Atoi(str[5:7]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m < 1 || m > 12 {
|
||||||
|
err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n, err = strconv.Atoi(str[8:10]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n < 1 || n > 31 {
|
||||||
|
err = fmt.Errorf("Invalid 0000-00-000 style DATE string: " + str)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Year = int16(y)
|
||||||
|
d.Month = byte(m)
|
||||||
|
d.Day = byte(n)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserCred struct {
|
||||||
|
AWS string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stmt struct {
|
||||||
|
Sid string
|
||||||
|
Effect string
|
||||||
|
Principal UserCred
|
||||||
|
Action []string
|
||||||
|
Resource []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BucketPolicy struct {
|
||||||
|
Version string // date in 0000-00-00 format
|
||||||
|
Statement []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add more checks
|
||||||
|
|
||||||
|
// validate request body is proper JSON
|
||||||
|
func Parsepolicy(data io.Reader) (BucketPolicy, bool) {
|
||||||
|
var policy BucketPolicy
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
err := decoder.Decode(&policy)
|
||||||
|
if err != nil {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(policy.Version) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
_, err = ParseDate(policy.Version)
|
||||||
|
if err != nil {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(policy.Statement) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, statement := range policy.Statement {
|
||||||
|
if len(statement.Sid) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(statement.Effect) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(statement.Principal.AWS) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(statement.Action) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
if len(statement.Resource) == 0 {
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return policy, true
|
||||||
|
|
||||||
|
error:
|
||||||
|
return BucketPolicy{}, false
|
||||||
|
}
|
Loading…
Reference in New Issue