Merge pull request #164 from harshavardhana/pr_out_implement_bucket_policy_handler

This commit is contained in:
Harshavardhana 2015-02-16 18:28:48 -08:00
commit 4ce180bb72
18 changed files with 827 additions and 407 deletions

235
pkg/api/minioapi/buckets.go Normal file
View File

@ -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))
}
}
}

View File

@ -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,

View File

@ -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

View File

@ -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
}

View File

@ -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")

143
pkg/api/minioapi/objects.go Normal file
View File

@ -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))
}
}
}

View File

@ -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) {
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)

View File

@ -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
} }

View File

@ -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)

View File

@ -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")

View File

@ -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
} }

60
pkg/utils/policy/date.go Normal file
View File

@ -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
}

View File

@ -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
}